1// List.cpp
2
3#include "StdAfx.h"
4
5#include "Common/IntToString.h"
6#include "Common/MyCom.h"
7#include "Common/StdOutStream.h"
8#include "Common/StringConvert.h"
9
10#include "Windows/Error.h"
11#include "Windows/FileDir.h"
12#include "Windows/PropVariant.h"
13#include "Windows/PropVariantConversions.h"
14
15#include "../../Archive/IArchive.h"
16
17#include "../Common/OpenArchive.h"
18#include "../Common/PropIDUtils.h"
19
20#include "ConsoleClose.h"
21#include "List.h"
22#include "OpenCallbackConsole.h"
23
24using namespace NWindows;
25
26struct CPropIdToName
27{
28  PROPID PropID;
29  const wchar_t *Name;
30};
31
32static const CPropIdToName kPropIdToName[] =
33{
34  { kpidPath, L"Path" },
35  { kpidName, L"Name" },
36  { kpidIsDir, L"Folder" },
37  { kpidSize, L"Size" },
38  { kpidPackSize, L"Packed Size" },
39  { kpidAttrib, L"Attributes" },
40  { kpidCTime, L"Created" },
41  { kpidATime, L"Accessed" },
42  { kpidMTime, L"Modified" },
43  { kpidSolid, L"Solid" },
44  { kpidCommented, L"Commented" },
45  { kpidEncrypted, L"Encrypted" },
46  { kpidSplitBefore, L"Split Before" },
47  { kpidSplitAfter, L"Split After" },
48  { kpidDictionarySize, L"Dictionary Size" },
49  { kpidCRC, L"CRC" },
50  { kpidType, L"Type" },
51  { kpidIsAnti, L"Anti" },
52  { kpidMethod, L"Method" },
53  { kpidHostOS, L"Host OS" },
54  { kpidFileSystem, L"File System" },
55  { kpidUser, L"User" },
56  { kpidGroup, L"Group" },
57  { kpidBlock, L"Block" },
58  { kpidComment, L"Comment" },
59  { kpidPosition, L"Position" },
60  { kpidPrefix, L"Prefix" },
61  { kpidNumSubDirs, L"Folders" },
62  { kpidNumSubFiles, L"Files" },
63  { kpidUnpackVer, L"Version" },
64  { kpidVolume, L"Volume" },
65  { kpidIsVolume, L"Multivolume" },
66  { kpidOffset, L"Offset" },
67  { kpidLinks, L"Links" },
68  { kpidNumBlocks, L"Blocks" },
69  { kpidNumVolumes, L"Volumes" },
70
71  { kpidBit64, L"64-bit" },
72  { kpidBigEndian, L"Big-endian" },
73  { kpidCpu, L"CPU" },
74  { kpidPhySize, L"Physical Size" },
75  { kpidHeadersSize, L"Headers Size" },
76  { kpidChecksum, L"Checksum" },
77  { kpidCharacts, L"Characteristics" },
78  { kpidVa, L"Virtual Address" },
79  { kpidId, L"ID" },
80  { kpidShortName, L"Short Name" },
81  { kpidCreatorApp, L"Creator Application"},
82  { kpidSectorSize, L"Sector Size" },
83  { kpidPosixAttrib, L"Mode" },
84  { kpidLink, L"Link" },
85  { kpidError, L"Error" },
86
87  { kpidTotalSize, L"Total Size" },
88  { kpidFreeSpace, L"Free Space" },
89  { kpidClusterSize, L"Cluster Size" },
90  { kpidVolumeName, L"Label" }
91};
92
93static const char kEmptyAttribChar = '.';
94
95static const char *kListing = "Listing archive: ";
96static const wchar_t *kFilesMessage = L"files";
97static const wchar_t *kDirsMessage = L"folders";
98
99static void GetAttribString(DWORD wa, bool isDir, char *s)
100{
101  s[0] = ((wa & FILE_ATTRIBUTE_DIRECTORY) != 0 || isDir) ? 'D' : kEmptyAttribChar;
102  s[1] = ((wa & FILE_ATTRIBUTE_READONLY) != 0) ? 'R': kEmptyAttribChar;
103  s[2] = ((wa & FILE_ATTRIBUTE_HIDDEN) != 0) ? 'H': kEmptyAttribChar;
104  s[3] = ((wa & FILE_ATTRIBUTE_SYSTEM) != 0) ? 'S': kEmptyAttribChar;
105  s[4] = ((wa & FILE_ATTRIBUTE_ARCHIVE) != 0) ? 'A': kEmptyAttribChar;
106  s[5] = '\0';
107}
108
109enum EAdjustment
110{
111  kLeft,
112  kCenter,
113  kRight
114};
115
116struct CFieldInfo
117{
118  PROPID PropID;
119  UString Name;
120  EAdjustment TitleAdjustment;
121  EAdjustment TextAdjustment;
122  int PrefixSpacesWidth;
123  int Width;
124};
125
126struct CFieldInfoInit
127{
128  PROPID PropID;
129  const wchar_t *Name;
130  EAdjustment TitleAdjustment;
131  EAdjustment TextAdjustment;
132  int PrefixSpacesWidth;
133  int Width;
134};
135
136static CFieldInfoInit kStandardFieldTable[] =
137{
138  { kpidMTime, L"   Date      Time", kLeft, kLeft, 0, 19 },
139  { kpidAttrib, L"Attr", kRight, kCenter, 1, 5 },
140  { kpidSize, L"Size", kRight, kRight, 1, 12 },
141  { kpidPackSize, L"Compressed", kRight, kRight, 1, 12 },
142  { kpidPath, L"Name", kLeft, kLeft, 2, 24 }
143};
144
145static void PrintSpaces(int numSpaces)
146{
147  for (int i = 0; i < numSpaces; i++)
148    g_StdOut << ' ';
149}
150
151static void PrintString(EAdjustment adjustment, int width, const UString &textString)
152{
153  const int numSpaces = width - textString.Length();
154  int numLeftSpaces = 0;
155  switch (adjustment)
156  {
157    case kLeft:
158      numLeftSpaces = 0;
159      break;
160    case kCenter:
161      numLeftSpaces = numSpaces / 2;
162      break;
163    case kRight:
164      numLeftSpaces = numSpaces;
165      break;
166  }
167  PrintSpaces(numLeftSpaces);
168  g_StdOut << textString;
169  PrintSpaces(numSpaces - numLeftSpaces);
170}
171
172class CFieldPrinter
173{
174  CObjectVector<CFieldInfo> _fields;
175public:
176  void Clear() { _fields.Clear(); }
177  void Init(const CFieldInfoInit *standardFieldTable, int numItems);
178  HRESULT Init(IInArchive *archive);
179  void PrintTitle();
180  void PrintTitleLines();
181  HRESULT PrintItemInfo(const CArc &arc, UInt32 index, bool techMode);
182  HRESULT PrintSummaryInfo(UInt64 numFiles, UInt64 numDirs,
183      const UInt64 *size, const UInt64 *compressedSize);
184};
185
186void CFieldPrinter::Init(const CFieldInfoInit *standardFieldTable, int numItems)
187{
188  Clear();
189  for (int i = 0; i < numItems; i++)
190  {
191    CFieldInfo fieldInfo;
192    const CFieldInfoInit &fieldInfoInit = standardFieldTable[i];
193    fieldInfo.PropID = fieldInfoInit.PropID;
194    fieldInfo.Name = fieldInfoInit.Name;
195    fieldInfo.TitleAdjustment = fieldInfoInit.TitleAdjustment;
196    fieldInfo.TextAdjustment = fieldInfoInit.TextAdjustment;
197    fieldInfo.PrefixSpacesWidth = fieldInfoInit.PrefixSpacesWidth;
198    fieldInfo.Width = fieldInfoInit.Width;
199    _fields.Add(fieldInfo);
200  }
201}
202
203static UString GetPropName(PROPID propID, BSTR name)
204{
205  for (int i = 0; i < sizeof(kPropIdToName) / sizeof(kPropIdToName[0]); i++)
206  {
207    const CPropIdToName &propIdToName = kPropIdToName[i];
208    if (propIdToName.PropID == propID)
209      return propIdToName.Name;
210  }
211  if (name)
212    return name;
213  wchar_t s[16];
214  ConvertUInt32ToString(propID, s);
215  return s;
216}
217
218HRESULT CFieldPrinter::Init(IInArchive *archive)
219{
220  Clear();
221  UInt32 numProps;
222  RINOK(archive->GetNumberOfProperties(&numProps));
223  for (UInt32 i = 0; i < numProps; i++)
224  {
225    CMyComBSTR name;
226    PROPID propID;
227    VARTYPE vt;
228    RINOK(archive->GetPropertyInfo(i, &name, &propID, &vt));
229    CFieldInfo fieldInfo;
230    fieldInfo.PropID = propID;
231    fieldInfo.Name = GetPropName(propID, name);
232    _fields.Add(fieldInfo);
233  }
234  return S_OK;
235}
236
237void CFieldPrinter::PrintTitle()
238{
239  for (int i = 0; i < _fields.Size(); i++)
240  {
241    const CFieldInfo &fieldInfo = _fields[i];
242    PrintSpaces(fieldInfo.PrefixSpacesWidth);
243    PrintString(fieldInfo.TitleAdjustment,
244      ((fieldInfo.PropID == kpidPath) ? 0: fieldInfo.Width), fieldInfo.Name);
245  }
246}
247
248void CFieldPrinter::PrintTitleLines()
249{
250  for (int i = 0; i < _fields.Size(); i++)
251  {
252    const CFieldInfo &fieldInfo = _fields[i];
253    PrintSpaces(fieldInfo.PrefixSpacesWidth);
254    for (int i = 0; i < fieldInfo.Width; i++)
255      g_StdOut << '-';
256  }
257}
258
259
260static BOOL IsFileTimeZero(CONST FILETIME *lpFileTime)
261{
262  return (lpFileTime->dwLowDateTime == 0) && (lpFileTime->dwHighDateTime == 0);
263}
264
265static const char *kEmptyTimeString = "                   ";
266static void PrintTime(const NCOM::CPropVariant &prop)
267{
268  if (prop.vt != VT_FILETIME)
269    throw "incorrect item";
270  if (IsFileTimeZero(&prop.filetime))
271    g_StdOut << kEmptyTimeString;
272  else
273  {
274    FILETIME localFileTime;
275    if (!FileTimeToLocalFileTime(&prop.filetime, &localFileTime))
276      throw "FileTimeToLocalFileTime error";
277    char s[32];
278    if (ConvertFileTimeToString(localFileTime, s, true, true))
279      g_StdOut << s;
280    else
281      g_StdOut << kEmptyTimeString;
282  }
283}
284
285HRESULT CFieldPrinter::PrintItemInfo(const CArc &arc, UInt32 index, bool techMode)
286{
287  /*
288  if (techMode)
289  {
290    g_StdOut << "Index = ";
291    g_StdOut << (UInt64)index;
292    g_StdOut << endl;
293  }
294  */
295  for (int i = 0; i < _fields.Size(); i++)
296  {
297    const CFieldInfo &fieldInfo = _fields[i];
298    if (!techMode)
299      PrintSpaces(fieldInfo.PrefixSpacesWidth);
300
301    NCOM::CPropVariant prop;
302    if (fieldInfo.PropID == kpidPath)
303    {
304      UString s;
305      RINOK(arc.GetItemPath(index, s));
306      prop = s;
307    }
308    else
309    {
310      RINOK(arc.Archive->GetProperty(index, fieldInfo.PropID, &prop));
311    }
312    if (techMode)
313    {
314      g_StdOut << fieldInfo.Name << " = ";
315    }
316    int width = (fieldInfo.PropID == kpidPath) ? 0: fieldInfo.Width;
317    if (fieldInfo.PropID == kpidAttrib && (prop.vt == VT_EMPTY || prop.vt == VT_UI4))
318    {
319      UInt32 attrib = (prop.vt == VT_EMPTY) ? 0 : prop.ulVal;
320      bool isFolder;
321      RINOK(IsArchiveItemFolder(arc.Archive, index, isFolder));
322      char s[8];
323      GetAttribString(attrib, isFolder, s);
324      g_StdOut << s;
325    }
326    else if (prop.vt == VT_EMPTY)
327    {
328      if (!techMode)
329        PrintSpaces(width);
330    }
331    else if (fieldInfo.PropID == kpidMTime)
332    {
333      PrintTime(prop);
334    }
335    else if (prop.vt == VT_BSTR)
336    {
337      if (techMode)
338        g_StdOut << prop.bstrVal;
339      else
340        PrintString(fieldInfo.TextAdjustment, width, prop.bstrVal);
341    }
342    else
343    {
344      UString s = ConvertPropertyToString(prop, fieldInfo.PropID);
345      s.Replace(wchar_t(0xA), L' ');
346      s.Replace(wchar_t(0xD), L' ');
347
348      if (techMode)
349        g_StdOut << s;
350      else
351        PrintString(fieldInfo.TextAdjustment, width, s);
352    }
353    if (techMode)
354      g_StdOut << endl;
355  }
356  return S_OK;
357}
358
359static void PrintNumberString(EAdjustment adjustment, int width, const UInt64 *value)
360{
361  wchar_t textString[32] = { 0 };
362  if (value != NULL)
363    ConvertUInt64ToString(*value, textString);
364  PrintString(adjustment, width, textString);
365}
366
367
368HRESULT CFieldPrinter::PrintSummaryInfo(UInt64 numFiles, UInt64 numDirs,
369    const UInt64 *size, const UInt64 *compressedSize)
370{
371  for (int i = 0; i < _fields.Size(); i++)
372  {
373    const CFieldInfo &fieldInfo = _fields[i];
374    PrintSpaces(fieldInfo.PrefixSpacesWidth);
375    NCOM::CPropVariant prop;
376    if (fieldInfo.PropID == kpidSize)
377      PrintNumberString(fieldInfo.TextAdjustment, fieldInfo.Width, size);
378    else if (fieldInfo.PropID == kpidPackSize)
379      PrintNumberString(fieldInfo.TextAdjustment, fieldInfo.Width, compressedSize);
380    else if (fieldInfo.PropID == kpidPath)
381    {
382      wchar_t textString[32];
383      ConvertUInt64ToString(numFiles, textString);
384      UString temp = textString;
385      temp += L" ";
386      temp += kFilesMessage;
387      temp += L", ";
388      ConvertUInt64ToString(numDirs, textString);
389      temp += textString;
390      temp += L" ";
391      temp += kDirsMessage;
392      PrintString(fieldInfo.TextAdjustment, 0, temp);
393    }
394    else
395      PrintString(fieldInfo.TextAdjustment, fieldInfo.Width, L"");
396  }
397  return S_OK;
398}
399
400bool GetUInt64Value(IInArchive *archive, UInt32 index, PROPID propID, UInt64 &value)
401{
402  NCOM::CPropVariant prop;
403  if (archive->GetProperty(index, propID, &prop) != S_OK)
404    throw "GetPropertyValue error";
405  if (prop.vt == VT_EMPTY)
406    return false;
407  value = ConvertPropVariantToUInt64(prop);
408  return true;
409}
410
411static void PrintPropPair(const wchar_t *name, const wchar_t *value)
412{
413  g_StdOut << name << " = " << value << endl;
414}
415
416HRESULT ListArchives(CCodecs *codecs, const CIntVector &formatIndices,
417    bool stdInMode,
418    UStringVector &arcPaths, UStringVector &arcPathsFull,
419    const NWildcard::CCensorNode &wildcardCensor,
420    bool enableHeaders, bool techMode,
421    #ifndef _NO_CRYPTO
422    bool &passwordEnabled, UString &password,
423    #endif
424    UInt64 &numErrors)
425{
426  numErrors = 0;
427  CFieldPrinter fieldPrinter;
428  if (!techMode)
429    fieldPrinter.Init(kStandardFieldTable, sizeof(kStandardFieldTable) / sizeof(kStandardFieldTable[0]));
430
431  UInt64 numFiles2 = 0, numDirs2 = 0, totalPackSize2 = 0, totalUnPackSize2 = 0;
432  UInt64 *totalPackSizePointer2 = 0, *totalUnPackSizePointer2 = 0;
433  int numArcs = /* stdInMode ? 1 : */ arcPaths.Size();
434  for (int i = 0; i < numArcs; i++)
435  {
436    const UString &archiveName = arcPaths[i];
437    UInt64 arcPackSize = 0;
438    if (!stdInMode)
439    {
440      NFile::NFind::CFileInfoW fi;
441      if (!fi.Find(archiveName) || fi.IsDir())
442      {
443        g_StdOut << endl << "Error: " << archiveName << " is not file" << endl;
444        numErrors++;
445        continue;
446      }
447      arcPackSize = fi.Size;
448    }
449
450    CArchiveLink archiveLink;
451
452    COpenCallbackConsole openCallback;
453    openCallback.OutStream = &g_StdOut;
454
455    #ifndef _NO_CRYPTO
456
457    openCallback.PasswordIsDefined = passwordEnabled;
458    openCallback.Password = password;
459
460    #endif
461
462    HRESULT result = archiveLink.Open2(codecs, formatIndices, stdInMode, NULL, archiveName, &openCallback);
463    if (result != S_OK)
464    {
465      if (result == E_ABORT)
466        return result;
467      g_StdOut << endl << "Error: " << archiveName << ": ";
468      if (result == S_FALSE)
469      {
470        #ifndef _NO_CRYPTO
471        if (openCallback.Open_WasPasswordAsked())
472          g_StdOut << "Can not open encrypted archive. Wrong password?";
473        else
474        #endif
475          g_StdOut << "Can not open file as archive";
476      }
477      else if (result == E_OUTOFMEMORY)
478        g_StdOut << "Can't allocate required memory";
479      else
480        g_StdOut << NError::MyFormatMessage(result);
481      g_StdOut << endl;
482      numErrors++;
483      continue;
484    }
485
486    if (!stdInMode)
487    for (int v = 0; v < archiveLink.VolumePaths.Size(); v++)
488    {
489      int index = arcPathsFull.FindInSorted(archiveLink.VolumePaths[v]);
490      if (index >= 0 && index > i)
491      {
492        arcPaths.Delete(index);
493        arcPathsFull.Delete(index);
494        numArcs = arcPaths.Size();
495      }
496    }
497
498    if (enableHeaders)
499    {
500      g_StdOut << endl << kListing << archiveName << endl << endl;
501
502      for (int i = 0; i < archiveLink.Arcs.Size(); i++)
503      {
504        const CArc &arc = archiveLink.Arcs[i];
505
506        g_StdOut << "--\n";
507        PrintPropPair(L"Path", arc.Path);
508        PrintPropPair(L"Type", codecs->Formats[arc.FormatIndex].Name);
509        if (!arc.ErrorMessage.IsEmpty())
510          PrintPropPair(L"Error", arc.ErrorMessage);
511        UInt32 numProps;
512        IInArchive *archive = arc.Archive;
513        if (archive->GetNumberOfArchiveProperties(&numProps) == S_OK)
514        {
515          for (UInt32 j = 0; j < numProps; j++)
516          {
517            CMyComBSTR name;
518            PROPID propID;
519            VARTYPE vt;
520            RINOK(archive->GetArchivePropertyInfo(j, &name, &propID, &vt));
521            NCOM::CPropVariant prop;
522            RINOK(archive->GetArchiveProperty(propID, &prop));
523            UString s = ConvertPropertyToString(prop, propID);
524            if (!s.IsEmpty())
525              PrintPropPair(GetPropName(propID, name), s);
526          }
527        }
528        if (i != archiveLink.Arcs.Size() - 1)
529        {
530          UInt32 numProps;
531          g_StdOut << "----\n";
532          if (archive->GetNumberOfProperties(&numProps) == S_OK)
533          {
534            UInt32 mainIndex = archiveLink.Arcs[i + 1].SubfileIndex;
535            for (UInt32 j = 0; j < numProps; j++)
536            {
537              CMyComBSTR name;
538              PROPID propID;
539              VARTYPE vt;
540              RINOK(archive->GetPropertyInfo(j, &name, &propID, &vt));
541              NCOM::CPropVariant prop;
542              RINOK(archive->GetProperty(mainIndex, propID, &prop));
543              UString s = ConvertPropertyToString(prop, propID);
544              if (!s.IsEmpty())
545                PrintPropPair(GetPropName(propID, name), s);
546            }
547          }
548        }
549
550      }
551      g_StdOut << endl;
552      if (techMode)
553        g_StdOut << "----------\n";
554    }
555
556    if (enableHeaders && !techMode)
557    {
558      fieldPrinter.PrintTitle();
559      g_StdOut << endl;
560      fieldPrinter.PrintTitleLines();
561      g_StdOut << endl;
562    }
563
564    const CArc &arc = archiveLink.Arcs.Back();
565    IInArchive *archive = arc.Archive;
566    if (techMode)
567    {
568      RINOK(fieldPrinter.Init(archive));
569    }
570    UInt64 numFiles = 0, numDirs = 0, totalPackSize = 0, totalUnPackSize = 0;
571    UInt64 *totalPackSizePointer = 0, *totalUnPackSizePointer = 0;
572    UInt32 numItems;
573    RINOK(archive->GetNumberOfItems(&numItems));
574    for (UInt32 i = 0; i < numItems; i++)
575    {
576      if (NConsoleClose::TestBreakSignal())
577        return E_ABORT;
578
579      UString filePath;
580      HRESULT res = arc.GetItemPath(i, filePath);
581      if (stdInMode && res == E_INVALIDARG)
582        break;
583      RINOK(res);
584
585      bool isFolder;
586      RINOK(IsArchiveItemFolder(archive, i, isFolder));
587      if (!wildcardCensor.CheckPath(filePath, !isFolder))
588        continue;
589
590      fieldPrinter.PrintItemInfo(arc, i, techMode);
591
592      UInt64 packSize, unpackSize;
593      if (!GetUInt64Value(archive, i, kpidSize, unpackSize))
594        unpackSize = 0;
595      else
596        totalUnPackSizePointer = &totalUnPackSize;
597      if (!GetUInt64Value(archive, i, kpidPackSize, packSize))
598        packSize = 0;
599      else
600        totalPackSizePointer = &totalPackSize;
601
602      g_StdOut << endl;
603
604      if (isFolder)
605        numDirs++;
606      else
607        numFiles++;
608      totalPackSize += packSize;
609      totalUnPackSize += unpackSize;
610    }
611
612    if (!stdInMode && totalPackSizePointer == 0)
613    {
614      if (archiveLink.VolumePaths.Size() != 0)
615        arcPackSize += archiveLink.VolumesSize;
616      totalPackSize = (numFiles == 0) ? 0 : arcPackSize;
617      totalPackSizePointer = &totalPackSize;
618    }
619    if (totalUnPackSizePointer == 0 && numFiles == 0)
620    {
621      totalUnPackSize = 0;
622      totalUnPackSizePointer = &totalUnPackSize;
623    }
624    if (enableHeaders && !techMode)
625    {
626      fieldPrinter.PrintTitleLines();
627      g_StdOut << endl;
628      fieldPrinter.PrintSummaryInfo(numFiles, numDirs, totalUnPackSizePointer, totalPackSizePointer);
629      g_StdOut << endl;
630    }
631    if (totalPackSizePointer != 0)
632    {
633      totalPackSizePointer2 = &totalPackSize2;
634      totalPackSize2 += totalPackSize;
635    }
636    if (totalUnPackSizePointer != 0)
637    {
638      totalUnPackSizePointer2 = &totalUnPackSize2;
639      totalUnPackSize2 += totalUnPackSize;
640    }
641    numFiles2 += numFiles;
642    numDirs2 += numDirs;
643  }
644  if (enableHeaders && !techMode && numArcs > 1)
645  {
646    g_StdOut << endl;
647    fieldPrinter.PrintTitleLines();
648    g_StdOut << endl;
649    fieldPrinter.PrintSummaryInfo(numFiles2, numDirs2, totalUnPackSizePointer2, totalPackSizePointer2);
650    g_StdOut << endl;
651    g_StdOut << "Archives: " << numArcs << endl;
652  }
653  return S_OK;
654}
655