OSDN Git Service

resource.h: Add IDS_PLUGIN_DESCRIPTION*
[winmerge-jp/winmerge-jp.git] / Src / 7zCommon.cpp
1 /////////////////////////////////////////////////////////////////////////////\r
2 //    WinMerge:  an interactive diff/merge utility\r
3 //    Copyright (C) 1997-2000  Thingamahoochie Software\r
4 //    Author: Dean Grimm\r
5 //    SPDX-License-Identifier: GPL-2.0-or-later\r
6 //\r
7 /////////////////////////////////////////////////////////////////////////////\r
8 \r
9 /* 7zCommon.cpp: Implement 7z related classes and functions\r
10  * Copyright (c) 2003 Jochen Tucht\r
11  *\r
12  * Remarks:     Different versions of 7-Zip are interfaced through specific\r
13  *                      versions of Merge7z (Merge7z311.dll, Merge7z312.dll, etc.)\r
14  *                      WinMerge can either use an installed copy of the 7-Zip software,\r
15  *                      or fallback to a local set of 7-Zip DLLs, which are to be included\r
16  *                      in the WinMerge binary distribution.\r
17  *\r
18  *                      Fallback policies are as follows:\r
19  *\r
20  *                      1. Detect 7-Zip version installed (XXX).\r
21  *                      2. If there is a Merge7zXXX.dll, be happy to use it\r
22  *                      3. Detect 7-Zip version from WinMerge distribution (YYY).\r
23  *                      4. If there is a Merge7zYYY.dll, be happy to use it.\r
24  *                      5. Sorry, no way.\r
25  *\r
26  *                      These rules can be customized by setting a registry variable\r
27  *                      *Merge7z/Enable* of type DWORD to one of the following values:\r
28  *\r
29  *                      0 - Entirely disable 7-Zip integration.\r
30  *                      1 - Use installed 7-Zip if present. Otherwise, use local 7-Zip.\r
31  *                      2 - Always use local 7-Zip.\r
32  *\r
33 \r
34 Please mind 2. a) of the GNU General Public License, and log your changes below.\r
35 \r
36 DATE:           BY:                                     DESCRIPTION:\r
37 ==========      ==================      ================================================\r
38 2003-12-09      Jochen Tucht            Created\r
39 2003-12-16      Jochen Tucht            Properly generate .tar.gz and .tar.bz2\r
40 2003-12-16      Jochen Tucht            Obtain long path to temporary folder\r
41 2004-01-20      Jochen Tucht            Complain only once if Merge7z*.dll is missing\r
42 2004-01-25      Jochen Tucht            Fix bad default for OPENFILENAME::nFilterIndex\r
43 2004-03-15      Jochen Tucht            Fix Visual Studio 2003 build issue\r
44 2004-04-13      Jochen Tucht            Avoid StrNCat to get away with shlwapi 4.70\r
45 2004-08-25      Jochen Tucht            More explicit error message\r
46 2004-10-17      Jochen Tucht            Leave decision whether to recurse into folders\r
47                                                                 to enumerator (Mask.Recurse)\r
48 2004-11-03      Jochen Tucht            FIX [1048997] as proposed by Kimmo 2004-11-02\r
49 2005-01-15      Jochen Tucht            Read 7-Zip version from 7zip_pad.xml\r
50                                                                 Set Merge7z UI language if DllBuild_Merge7z >= 9\r
51 2005-01-22      Jochen Tucht            Better explain what's present/missing/outdated\r
52 2005-02-05      Jochen Tucht            Fall back to IDD_MERGE7ZMISMATCH template from\r
53                                                                 .exe if .lang file isn't up to date.\r
54 2005-02-26      Jochen Tucht            Add download link to error message\r
55 2005-02-26      Jochen Tucht            Use WinAPI to obtain ISO language/region codes\r
56 2005-02-27      Jochen Tucht            FIX [1152375]\r
57 2005-04-24      Kimmo Varis                     Don't use DiffContext exported from DirView\r
58 2005-06-08      Kimmo Varis                     Use DIFFITEM, not reference to it (hopefully only\r
59                                                                 temporarily, to sort out new directory compare)\r
60 2005-06-22      Jochen Tucht            Change recommended version of 7-Zip to 4.20\r
61                                                                 Remove noise from Nagbox\r
62 2005-07-03      Jochen Tucht            DIFFITEM has changed due to RFE [ 1205516 ]\r
63 2005-07-04      Jochen Tucht            New global ArchiveGuessFormat() checks for\r
64                                                                 formats to be handled by external command line\r
65                                                                 tools. These take precedence over Merge7z\r
66                                                                 internal handlers.\r
67 2005-07-05      Jochen Tucht            Move to Merge7z::Format::GetDefaultName() to\r
68                                                                 build intermediate filenames for multi-step\r
69                                                                 compression.\r
70 2005-07-15      Jochen Tucht            Remove external command line tool integration\r
71                                                                 for now. Rethink about it after 2.4 branch.\r
72 2005-08-20      Jochen Tucht            Option to guess archive format by signature\r
73                                                                 Map extensions through ExternalArchiveFormat.ini\r
74 2005-08-23      Jochen Tucht            Option to entirely disable 7-Zip integration\r
75 2007-01-04      Kimmo Varis                     Convert using COptionsMgr for options.\r
76 2007-06-16      Jochen Neubeck          FIX [1723263] "Zip --> Both" operation...\r
77 2007-12-22      Jochen Neubeck          Fix Merge7z UI lang for new translation system\r
78                                                                 Change recommended version of 7-Zip to 4.57\r
79 2010-05-16      Jochen Neubeck          Read 7-Zip version from 7z.dll (which has long\r
80                                                                 ago replaced the various format and codec DLLs)\r
81                                                                 Change recommended version of 7-Zip to 4.65\r
82 */\r
83 \r
84 \r
85 #include "stdafx.h"\r
86 #include "7zCommon.h"\r
87 #include <afxinet.h>\r
88 #include "OptionsDef.h"\r
89 #include "OptionsMgr.h"\r
90 #include "DirView.h"\r
91 #include "DirDoc.h"\r
92 #include "DirActions.h"\r
93 //#include "ExternalArchiveFormat.h"\r
94 #include "VersionInfo.h"\r
95 #include "paths.h"\r
96 #include "Environment.h"\r
97 #include "Merge7zFormatRegister.h"\r
98 \r
99 #ifdef _DEBUG\r
100 #define new DEBUG_NEW\r
101 #endif\r
102 \r
103 /**\r
104  * @brief Proxy for Merge7z\r
105  */\r
106 static __declspec(thread) Merge7z::Proxy m_Merge7z =\r
107 {\r
108         { 0, 0, DllBuild_Merge7z, },\r
109         "Merge7z\\Merge7z.dll",\r
110         "Merge7z",\r
111         nullptr\r
112 };\r
113 \r
114 std::vector<Merge7z::Format *(*)(const String& path)> Merge7zFormatRegister::optionalFormats;\r
115 \r
116 /**\r
117  * @brief assign BSTR to String, and return BSTR for optional SysFreeString()\r
118  */\r
119 inline BSTR Assign(CString &dst, BSTR src)\r
120 {\r
121         dst = src;\r
122         return src;\r
123 }\r
124 \r
125 bool IsArchiveFile(const String& pszFile)\r
126 {\r
127         try {\r
128                 Merge7z::Format *piHandler = ArchiveGuessFormat(pszFile);\r
129                 if (piHandler != nullptr)\r
130                         return true;\r
131                 else\r
132                         return false;\r
133         }\r
134         catch (CException *e)\r
135         {\r
136                 e->Delete();\r
137                 return false;\r
138         }\r
139 }\r
140 \r
141 /**\r
142  * @brief Wrap Merge7z::GuessFormat() to allow for some customizing:\r
143  * - Check if 7-Zip integration is enabled.\r
144  * - Check for filename extension mappings.\r
145  */\r
146 Merge7z::Format *ArchiveGuessFormat(const String& path)\r
147 {\r
148         if (GetOptionsMgr()->GetInt(OPT_ARCHIVE_ENABLE) == 0)\r
149                 return nullptr;\r
150         if (paths::IsDirectory(path))\r
151                 return nullptr;\r
152         String path2(path);\r
153         // Map extensions through ExternalArchiveFormat.ini\r
154         static TCHAR null[] = _T("");\r
155         static const TCHAR section[] = _T("extensions");\r
156         String entry = paths::FindExtension(path);\r
157         TCHAR value[20];\r
158         static LPCTSTR filename = nullptr;\r
159         if (filename == nullptr)\r
160         {\r
161                 TCHAR cPath[INTERNET_MAX_PATH_LENGTH];\r
162                 DWORD cchPath = SearchPath(nullptr, _T("ExternalArchiveFormat.ini"), nullptr,\r
163                         INTERNET_MAX_PATH_LENGTH, cPath, nullptr);\r
164                 filename = cchPath && cchPath < INTERNET_MAX_PATH_LENGTH ? _tcsdup(cPath) : null;\r
165         }\r
166         if (*filename &&\r
167                 GetPrivateProfileString(section, entry.c_str(), null, value, 20, filename) &&\r
168                 *value == '.')\r
169         {\r
170                 // Remove end-of-line comments (in string returned from GetPrivateProfileString)\r
171                 // that is, remove semicolon & whatever follows it\r
172                 if (LPTSTR p = StrChr(value, ';'))\r
173                 {\r
174                         *p = '\0';\r
175                         StrTrim(value, _T(" \t"));\r
176                 }\r
177                 path2 = value;\r
178         }\r
179 \r
180         // PATCH [ 1229867 ] RFE [ 1205516 ], RFE [ 887948 ], and other issues\r
181         // command line integration portion is not yet applied\r
182         // so following code not yet valid, so temporarily commented out\r
183         // Look for command line tool first\r
184         /*Merge7z::Format *pFormat;\r
185         if (CExternalArchiveFormat::GuessFormat(path, pFormat))\r
186         {\r
187                 return pFormat;\r
188         }*/\r
189         // Default to Merge7z*.dll\r
190 \r
191         try\r
192         {\r
193                 Merge7z::Format *pFormat = m_Merge7z->GuessFormat(path2.c_str());\r
194                 if (pFormat == nullptr)\r
195                         pFormat = Merge7zFormatRegister::GuessFormat(path2);\r
196                 return pFormat;\r
197         }\r
198         catch (...)\r
199         {\r
200                 Merge7z::Format *pFormat = Merge7zFormatRegister::GuessFormat(path2);\r
201                 if (pFormat != nullptr)\r
202                         return pFormat;\r
203                 throw;\r
204         }\r
205 }\r
206 \r
207 /**\r
208  * @brief Check whether archive support is available.\r
209  */\r
210 int NTAPI HasZipSupport()\r
211 {\r
212         static int HasZipSupport = -1;\r
213         if (HasZipSupport == -1)\r
214         {\r
215                 try\r
216                 {\r
217                         m_Merge7z.operator->();\r
218                         HasZipSupport = 1;\r
219                 }\r
220                 catch (CException *e)\r
221                 {\r
222                         e->Delete();\r
223                         HasZipSupport = 0;\r
224                 }\r
225         }\r
226         return HasZipSupport;\r
227 }\r
228 \r
229 /**\r
230  * @brief Delete head of temp path context list, and return its parent context.\r
231  */\r
232 CTempPathContext *CTempPathContext::DeleteHead()\r
233 {\r
234         CTempPathContext *pParent = m_pParent;\r
235         delete this;\r
236         return pParent;\r
237 }\r
238 \r
239 void CTempPathContext::Swap(int idx1, int idx2)\r
240 {\r
241         std::swap(m_strDisplayRoot[idx1], m_strDisplayRoot[idx2]);\r
242         std::swap(m_strRoot[idx1], m_strRoot[idx2]);\r
243         if (m_pParent != nullptr)\r
244                 m_pParent->Swap(idx1, idx2);\r
245 }\r
246 \r
247 /**\r
248  * @brief Return installed or local version of 7-Zip.\r
249  */\r
250 DWORD NTAPI VersionOf7z()\r
251 {\r
252         String path = paths::ConcatPath(env::GetProgPath(), _T("Merge7z\\7z.dll"));\r
253         unsigned versionMS = 0;\r
254         unsigned versionLS = 0;\r
255         CVersionInfo(path.c_str()).GetFixedFileVersion(versionMS, versionLS);\r
256         return versionMS;\r
257 }\r
258 \r
259 /**\r
260  * @brief Access dll functions through proxy.\r
261  */\r
262 interface Merge7z *Merge7z::Proxy::operator->()\r
263 {\r
264         // As long as the Merge7z*.DLL has not yet been loaded, Merge7z\r
265         // [0] points to the name of the DLL (with placeholders for 7-\r
266         // Zip major and minor version numbers). Once the DLL has been\r
267         // loaded successfully, Merge7z[0] is set to nullptr, causing the\r
268         // if to fail on subsequent calls.\r
269 \r
270         if (const char *format = Merge7z[0])\r
271         {\r
272                 // Merge7z has not yet been loaded\r
273 \r
274                 if (GetOptionsMgr()->GetInt(OPT_ARCHIVE_ENABLE) == 0)\r
275                         throw new CResourceException();\r
276                 if (DWORD ver = VersionOf7z())\r
277                 {\r
278                         Merge7z[0] = format;\r
279                         stub.Load();\r
280                 }\r
281                 else\r
282                 {\r
283                         throw new CResourceException();\r
284                 }\r
285                 LANGID wLangID = (LANGID)GetThreadLocale();\r
286                 DWORD flags = Initialize::Default | Initialize::Local7z | (wLangID << 16);\r
287                 if (GetOptionsMgr()->GetBool(OPT_ARCHIVE_PROBETYPE))\r
288                 {\r
289                         flags |= Initialize::GuessFormatBySignature | Initialize::GuessFormatByExtension;\r
290                 }\r
291                 if (Merge7z[1])\r
292                         ((interface Merge7z *)Merge7z[1])->Initialize(flags);\r
293         }\r
294         return ((interface Merge7z *)Merge7z[1]);\r
295 }\r
296 \r
297 /**\r
298  * @brief Tell Merge7z we are going to enumerate just 1 item.\r
299  */\r
300 UINT SingleItemEnumerator::Open()\r
301 {\r
302         return 1;\r
303 }\r
304 \r
305 /**\r
306  * @brief Pass information about the item to Merge7z.\r
307  */\r
308 Merge7z::Envelope *SingleItemEnumerator::Enum(Item &item)\r
309 {\r
310         item.Mask.Item = item.Mask.Name|item.Mask.FullPath|item.Mask.Recurse;\r
311         item.Name = Name;\r
312         item.FullPath = FullPath;\r
313         return 0;\r
314 }\r
315 \r
316 /**\r
317  * @brief SingleFileEnumerator constructor.\r
318  */\r
319 SingleItemEnumerator::SingleItemEnumerator(LPCTSTR path, LPCTSTR FullPath, LPCTSTR Name)\r
320 : FullPath(FullPath)\r
321 , Name(Name)\r
322 {\r
323 }\r
324 \r
325 /**\r
326  * @brief Construct a DirItemEnumerator.\r
327  *\r
328  * Argument *nFlags* controls operation as follows:\r
329  * LVNI_ALL:            Enumerate all items.\r
330  * LVNI_SELECTED:       Enumerate selected items only.\r
331  * Original:            Set folder prefix for first iteration to "original"\r
332  * Altered:                     Set folder prefix for second iteration to "altered"\r
333  * BalanceFolders:      Ensure that all nonempty folders on either side have a\r
334  *                                      corresponding folder on the other side, even if it is\r
335  *                                      empty (DirScan doesn't recurse into folders which\r
336  *                                      appear only on one side).\r
337  * DiffsOnly:           Enumerate diffs only.\r
338  */\r
339 DirItemEnumerator::DirItemEnumerator(CDirView *pView, int nFlags)\r
340 : m_pView(pView)\r
341 , m_nFlags(nFlags)\r
342 {\r
343         if (m_nFlags & Original)\r
344         {\r
345                 m_rgFolderPrefix.push_back(_T("original"));\r
346         }\r
347         if (m_nFlags & Altered)\r
348         {\r
349                 m_rgFolderPrefix.push_back(_T("altered"));\r
350         }\r
351         if (m_nFlags & BalanceFolders)\r
352         {\r
353                 const CDiffContext& ctxt = pView->GetDiffContext();\r
354                 // Collect implied folders\r
355                 for (UINT i = Open() ; i-- ; )\r
356                 {\r
357                         const DIFFITEM &di = Next();\r
358                         if ((m_nFlags & DiffsOnly) && !IsItemNavigableDiff(ctxt, di))\r
359                         {\r
360                                 continue;\r
361                         }\r
362                         // Enumerating items\r
363                         if (di.diffcode.exists(m_index))\r
364                         {\r
365                                 // Item is present on right side, i.e. folder is implied\r
366                                 m_rgImpliedFolders[m_index][di.diffFileInfo[m_index].path.get()] = PVOID(1);\r
367                         }\r
368                 }\r
369         }\r
370 }\r
371 \r
372 /**\r
373  * @brief Initialize enumerator, return number of items to be enumerated.\r
374  */\r
375 UINT DirItemEnumerator::Open()\r
376 {\r
377         m_nIndex = -1;\r
378         m_curFolderPrefix = m_rgFolderPrefix.begin();\r
379         if (m_pView->GetDocument()->m_nDirs < 3)\r
380                 m_index = (m_nFlags & Right) != 0 ? 1 : 0;\r
381         else\r
382                 m_index = ((m_nFlags & Right) != 0) ? 2 : ((m_nFlags & Middle) != 0 ? 1 : 0);\r
383         size_t nrgFolderPrefix = m_rgFolderPrefix.size();\r
384         if (nrgFolderPrefix)\r
385         {\r
386                 m_strFolderPrefix = *m_curFolderPrefix++;\r
387         }\r
388         else\r
389         {\r
390                 nrgFolderPrefix = 1;\r
391         }\r
392         return\r
393         static_cast<UINT>((\r
394                 (m_nFlags & LVNI_SELECTED)\r
395         ?       pView(m_pView)->GetSelectedCount()\r
396         :       pView(m_pView)->GetItemCount()\r
397         ) * nrgFolderPrefix);\r
398 }\r
399 \r
400 /**\r
401  * @brief Return next item.\r
402  */\r
403 const DIFFITEM &DirItemEnumerator::Next()\r
404 {\r
405         enum {nMask = LVNI_FOCUSED|LVNI_SELECTED|LVNI_CUT|LVNI_DROPHILITED};\r
406         while ((m_nIndex = pView(m_pView)->GetNextItem(m_nIndex, m_nFlags & nMask)) == -1)\r
407         {\r
408                 m_strFolderPrefix = *m_curFolderPrefix++;\r
409                 m_index = 1;\r
410         }\r
411         return m_pView->GetDiffItem(m_nIndex);\r
412 }\r
413 \r
414 /**\r
415  * @brief Pass information about an item to Merge7z.\r
416  *\r
417  * Information is passed through struct Merge7z::DirItemEnumerator::Item.\r
418  * The *mask* member denotes which of the other members contain valid data.\r
419  * If *mask* is zero upon return, which will be the case if Enum() decides to\r
420  * leave the struct untouched, Merge7z will ignore the item.\r
421  * If Enum() allocates temporary storage for string members, it must also\r
422  * allocate an Envelope, providing a Free() method to free the temporary\r
423  * storage, along with the Envelope itself. The Envelope pointer is passed to\r
424  * Merge7z as the return value of the function. It is not meant to be a success\r
425  * indicator, so if no temporary storage is required, it is perfectly alright\r
426  * to return `nullptr`.\r
427  */\r
428 Merge7z::Envelope *DirItemEnumerator::Enum(Item &item)\r
429 {\r
430         const CDiffContext& ctxt = m_pView->GetDiffContext();\r
431         const DIFFITEM &di = Next();\r
432 \r
433         if ((m_nFlags & DiffsOnly) && !IsItemNavigableDiff(ctxt, di))\r
434         {\r
435                 return 0;\r
436         }\r
437 \r
438         bool isSideOnly = !di.diffcode.exists(m_index);\r
439 \r
440         Envelope *envelope = new Envelope;\r
441 \r
442         const String &sFilename = di.diffFileInfo[m_index].filename;\r
443         const String &sSubdir = di.diffFileInfo[m_index].path;\r
444         if (sSubdir.length())\r
445                 envelope->Name = paths::ConcatPath(sSubdir, sFilename);\r
446         else\r
447                 envelope->Name = sFilename;\r
448         envelope->FullPath = paths::ConcatPath(\r
449                         di.getFilepath(m_index, ctxt.GetNormalizedPath(m_index)),\r
450                         sFilename);\r
451 \r
452         UINT32 Recurse = item.Mask.Recurse;\r
453 \r
454         if (m_nFlags & BalanceFolders)\r
455         {\r
456                 // Enumerating items on right side\r
457                 if (isSideOnly)\r
458                 {\r
459                         // Item is missing on right side\r
460                         PVOID &implied = m_rgImpliedFolders[m_index][di.diffFileInfo[1-m_index].path.get()];\r
461                         if (implied == nullptr)\r
462                         {\r
463                                 // Folder is not implied by some other file, and has\r
464                                 // not been enumerated so far, so enumerate it now!\r
465                                 envelope->Name = di.diffFileInfo[1-m_index].path;\r
466                                 envelope->FullPath = di.getFilepath(1-m_index, ctxt.GetNormalizedPath(1-m_index));\r
467                                 implied = PVOID(2); // Don't enumerate same folder twice!\r
468                                 isSideOnly = false;\r
469                                 Recurse = 0;\r
470                         }\r
471                 }\r
472         }\r
473 \r
474         if (isSideOnly)\r
475         {\r
476                 return envelope;\r
477         }\r
478 \r
479         if (m_strFolderPrefix.length())\r
480         {\r
481                 if (envelope->Name.length())\r
482                         envelope->Name.insert(0, _T("\\"));\r
483                 envelope->Name.insert(0, m_strFolderPrefix);\r
484         }\r
485 \r
486         item.Mask.Item = item.Mask.Name|item.Mask.FullPath|item.Mask.CheckIfPresent|Recurse;\r
487         item.Name = envelope->Name.c_str();\r
488         item.FullPath = envelope->FullPath.c_str();\r
489         return envelope;\r
490 }\r
491 \r
492 /**\r
493  * @brief Apply appropriate handlers from left to right.\r
494  */\r
495 bool DirItemEnumerator::MultiStepCompressArchive(LPCTSTR path)\r
496 {\r
497         DeleteFile(path);\r
498         Merge7z::Format *piHandler = ArchiveGuessFormat(path);\r
499         if (piHandler != nullptr)\r
500         {\r
501                 HWND hwndOwner = CWnd::GetSafeOwner()->GetSafeHwnd();\r
502                 CString pathIntermediate;\r
503                 SysFreeString(Assign(pathIntermediate, piHandler->GetDefaultName(hwndOwner, path)));\r
504                 String pathPrepend = path;\r
505                 pathPrepend.resize(pathPrepend.rfind('\\') + 1);\r
506                 pathIntermediate.Insert(0, pathPrepend.c_str());\r
507                 bool bDone = MultiStepCompressArchive(pathIntermediate);\r
508                 if (bDone)\r
509                 {\r
510                         SingleItemEnumerator tmpEnumerator(path, pathIntermediate);\r
511                         piHandler->CompressArchive(hwndOwner, path, &tmpEnumerator);\r
512                         DeleteFile(pathIntermediate);\r
513                 }\r
514                 else\r
515                 {\r
516                         piHandler->CompressArchive(hwndOwner, path, this);\r
517                 }\r
518                 return true;\r
519         }\r
520         return false;\r
521 }\r
522 \r
523 /**\r
524  * @brief Generate archive from DirView items.\r
525  */\r
526 void DirItemEnumerator::CompressArchive(LPCTSTR path)\r
527 {\r
528         String strPath;\r
529         if (path == nullptr)\r
530         {\r
531                 // 7z311 can only write 7z, zip, and tar(.gz|.bz2) archives, so don't\r
532                 // offer other formats here!\r
533                 static const TCHAR _T_Filter[]\r
534                 (\r
535                         _T("7z|*.7z|")\r
536                         //_T("z|*.z|")\r
537                         _T("zip|*.zip|")\r
538                         _T("jar (zip)|*.jar|")\r
539                         _T("ear (zip)|*.ear|")\r
540                         _T("war (zip)|*.war|")\r
541                         _T("xpi (zip)|*.xpi|")\r
542                         //_T("rar|*.rar|")\r
543                         _T("tar|*.tar|")\r
544                         _T("tar.z|*.tar.z|")\r
545                         _T("tar.gz|*.tar.gz|")\r
546                         _T("tar.bz2|*.tar.bz2|")\r
547                         //_T("tz|*.tz|")\r
548                         _T("tgz|*.tgz|")\r
549                         _T("tbz2|*.tbz2|")\r
550                         //_T("lzh|*.lzh|")\r
551                         //_T("cab|*.cab|")\r
552                         //_T("arj|*.arj|")\r
553                         //_T("deb|*.deb|")\r
554                         //_T("rpm|*.rpm|")\r
555                         //_T("cpio|*.cpio|")\r
556                         //_T("|")\r
557                 );\r
558                 String strFilter; // = CExternalArchiveFormat::GetOpenFileFilterString();\r
559                 strFilter.insert(0, _T_Filter);\r
560                 strFilter += _T("|");\r
561                 CFileDialog dlg\r
562                 (\r
563                         FALSE,\r
564                         0,\r
565                         0,\r
566                         OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN,\r
567                         strFilter.c_str()\r
568                 );\r
569                 dlg.m_ofn.nFilterIndex = GetOptionsMgr()->GetInt(OPT_ARCHIVE_FILTER_INDEX);\r
570                 // Use extension from current filter as default extension:\r
571                 if (int i = dlg.m_ofn.nFilterIndex)\r
572                 {\r
573                         dlg.m_ofn.lpstrDefExt = dlg.m_ofn.lpstrFilter;\r
574                         while (*dlg.m_ofn.lpstrDefExt && --i)\r
575                         {\r
576                                 dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 1;\r
577                                 dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 1;\r
578                         }\r
579                         if (*dlg.m_ofn.lpstrDefExt)\r
580                         {\r
581                                 dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 3;\r
582                         }\r
583                 }\r
584                 if (dlg.DoModal() == IDOK)\r
585                 {\r
586                         strPath = dlg.GetPathName();\r
587                         path = strPath.c_str();\r
588                         GetOptionsMgr()->SaveOption(OPT_ARCHIVE_FILTER_INDEX, static_cast<int>(dlg.m_ofn.nFilterIndex));\r
589                 }\r
590         }\r
591         if (path && !MultiStepCompressArchive(path))\r
592         {\r
593                 LangMessageBox(IDS_UNKNOWN_ARCHIVE_FORMAT, MB_ICONEXCLAMATION);\r
594         }\r
595 }\r
596 \r
597 \r
598 DecompressResult DecompressArchive(HWND hWnd, const PathContext& files)\r
599 {\r
600         DecompressResult res(files, nullptr, paths::IS_EXISTING_DIR);\r
601         try\r
602         {\r
603                 String path;\r
604                 USES_CONVERSION;\r
605                 // Handle archives using 7-zip\r
606                 Merge7z::Format *piHandler;\r
607                 piHandler = ArchiveGuessFormat(res.files[0]);\r
608                 if (piHandler  != nullptr)\r
609                 {\r
610                         res.pTempPathContext = new CTempPathContext;\r
611                         path = env::GetTempChildPath();\r
612                         for (int index = 0; index < res.files.GetSize(); index++)\r
613                                 res.pTempPathContext->m_strDisplayRoot[index] = res.files[index];\r
614                         if (res.files.GetSize() == 2 && res.files[0] == res.files[1])\r
615                                 res.files[1].erase();\r
616                         do\r
617                         {\r
618                                 if (FAILED(piHandler->DeCompressArchive(hWnd, res.files[0].c_str(), path.c_str())))\r
619                                         break;\r
620                                 if (res.files[0].find(path) == 0)\r
621                                 {\r
622                                         VERIFY(::DeleteFile(res.files[0].c_str()) || (LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), res.files[0])), false));\r
623                                 }\r
624                                 BSTR pTmp = piHandler->GetDefaultName(hWnd, res.files[0].c_str());\r
625                                 res.files[0] = OLE2T(pTmp);\r
626                                 SysFreeString(pTmp);\r
627                                 res.files[0].insert(0, _T("\\"));\r
628                                 res.files[0].insert(0, path);\r
629                                 piHandler = ArchiveGuessFormat(res.files[0]);\r
630                         } while (piHandler != nullptr);\r
631                         res.files[0] = path;\r
632                 }\r
633                 piHandler = res.files[1].empty() ? nullptr\r
634                                                                                  : ArchiveGuessFormat(res.files[1]);\r
635                 if (piHandler != nullptr)\r
636                 {\r
637                         if (res.pTempPathContext == nullptr)\r
638                         {\r
639                                 res.pTempPathContext = new CTempPathContext;\r
640                                 for (int index = 0; index < res.files.GetSize(); index++)\r
641                                         res.pTempPathContext->m_strDisplayRoot[index] = res.files[index];\r
642                         }\r
643                         path = env::GetTempChildPath();\r
644                         do\r
645                         {\r
646                                 if (FAILED(piHandler->DeCompressArchive(hWnd, res.files[1].c_str(), path.c_str())))\r
647                                         break;;\r
648                                 if (res.files[1].find(path) == 0)\r
649                                 {\r
650                                         VERIFY(::DeleteFile(res.files[1].c_str()) || (LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), res.files[1])), false));\r
651                                 }\r
652                                 BSTR pTmp = piHandler->GetDefaultName(hWnd, res.files[1].c_str());\r
653                                 res.files[1] = OLE2T(pTmp);\r
654                                 SysFreeString(pTmp);\r
655                                 res.files[1].insert(0, _T("\\"));\r
656                                 res.files[1].insert(0, path);\r
657                                 piHandler = ArchiveGuessFormat(res.files[1]);\r
658                         } while (piHandler != nullptr);\r
659                         res.files[1] = path;\r
660                 }\r
661                 piHandler = (res.files.GetSize() <= 2) ? nullptr : ArchiveGuessFormat(res.files[2]);\r
662                 if (piHandler != nullptr)\r
663                 {\r
664                         if (res.pTempPathContext == nullptr)\r
665                         {\r
666                                 res.pTempPathContext = new CTempPathContext;\r
667                                 for (int index = 0; index < res.files.GetSize(); index++)\r
668                                         res.pTempPathContext->m_strDisplayRoot[index] = res.files[index];\r
669                         }\r
670                         path = env::GetTempChildPath();\r
671                         do\r
672                         {\r
673                                 if (FAILED(piHandler->DeCompressArchive(hWnd, res.files[2].c_str(), path.c_str())))\r
674                                         break;;\r
675                                 if (res.files[2].find(path) == 0)\r
676                                 {\r
677                                         VERIFY(::DeleteFile(res.files[2].c_str()) || (LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), res.files[2])), false));\r
678                                 }\r
679                                 BSTR pTmp = piHandler->GetDefaultName(hWnd, res.files[2].c_str());\r
680                                 res.files[2] = OLE2T(pTmp);\r
681                                 SysFreeString(pTmp);\r
682                                 res.files[2].insert(0, _T("\\"));\r
683                                 res.files[2].insert(0, path);\r
684                                 piHandler = ArchiveGuessFormat(res.files[2]);\r
685                         } while (piHandler != nullptr);\r
686                         res.files[2] = path;\r
687                 }\r
688                 if (res.files[1].empty())\r
689                 {\r
690                         // assume Perry style patch\r
691                         res.files[1] = path;\r
692                         res.files[0] += _T("\\ORIGINAL");\r
693                         res.files[1] += _T("\\ALTERED");\r
694                         if (!PathFileExists(res.files[0].c_str()) || !PathFileExists(res.files[1].c_str()))\r
695                         {\r
696                                 // not a Perry style patch: diff with itself...\r
697                                 res.files[0] = res.files[1] = path;\r
698                         }\r
699                         else\r
700                         {\r
701                                 res.pTempPathContext->m_strDisplayRoot[0] += _T("\\ORIGINAL");\r
702                                 res.pTempPathContext->m_strDisplayRoot[1] += _T("\\ALTERED");\r
703                         }\r
704                 }\r
705         }\r
706         catch (CException *e)\r
707         {\r
708                 e->Delete();\r
709         }\r
710         return res;\r
711 }\r
712 \r
713 \r