Merge pull request #2212 from unxed/ctrl_yo
[far2l.git] / far2l / src / dizlist.cpp
blob5d991beec4fa3bccf28f6118cd079f2ae6fe9b65
1 /*
2 dizlist.cpp
4 Описания файлов
5 */
6 /*
7 Copyright (c) 1996 Eugene Roshal
8 Copyright (c) 2000 Far Group
9 All rights reserved.
11 Redistribution and use in source and binary forms, with or without
12 modification, are permitted provided that the following conditions
13 are met:
14 1. Redistributions of source code must retain the above copyright
15 notice, this list of conditions and the following disclaimer.
16 2. Redistributions in binary form must reproduce the above copyright
17 notice, this list of conditions and the following disclaimer in the
18 documentation and/or other materials provided with the distribution.
19 3. The name of the authors may not be used to endorse or promote products
20 derived from this software without specific prior written permission.
22 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 #include "headers.hpp"
36 #include "dizlist.hpp"
37 #include "lang.hpp"
38 #include "savescr.hpp"
39 #include "TPreRedrawFunc.hpp"
40 #include "interf.hpp"
41 #include "keyboard.hpp"
42 #include "message.hpp"
43 #include "config.hpp"
44 #include "pathmix.hpp"
45 #include "strmix.hpp"
46 #include "filestr.hpp"
47 #include "codepage.hpp"
48 #include "cache.hpp"
50 static DizRecord *SearchDizData;
51 static int _cdecl SortDizIndex(const void *el1, const void *el2);
52 static int _cdecl SortDizSearch(const void *key, const void *elem);
53 struct DizSearchKey
55 const wchar_t *Str;
56 const int Len;
59 DizList::DizList()
61 DizData(nullptr),
62 DizCount(0),
63 IndexData(nullptr),
64 IndexCount(0),
65 Modified(false),
66 NeedRebuild(true),
67 OrigCodePage(CP_AUTODETECT),
68 AnsiBuf(nullptr)
71 DizList::~DizList()
73 Reset();
75 free(AnsiBuf);
78 void DizList::Reset()
80 if (DizData) {
81 for (int I = 0; I < DizCount; I++)
82 free(DizData[I].DizText);
84 free(DizData);
87 DizData = nullptr;
88 DizCount = 0;
90 free(IndexData);
92 IndexData = nullptr;
93 IndexCount = 0;
94 Modified = false;
95 NeedRebuild = true;
96 OrigCodePage = CP_AUTODETECT;
99 void DizList::PR_ReadingMsg()
101 Message(0, 0, L"", Msg::ReadingDiz);
104 void DizList::Read(const wchar_t *Path, const wchar_t *DizName)
106 Reset();
107 TPreRedrawFuncGuard preRedrawFuncGuard(DizList::PR_ReadingMsg);
108 const wchar_t *NamePtr = Opt.Diz.strListNames;
110 for (;;) {
111 if (DizName) {
112 strDizFileName = DizName;
113 } else {
114 strDizFileName = Path;
116 if (!PathCanHoldRegularFile(strDizFileName))
117 break;
119 FARString strArgName;
121 if (!(NamePtr = GetCommaWord(NamePtr, strArgName)))
122 break;
124 AddEndSlash(strDizFileName);
125 strDizFileName+= strArgName;
128 File DizFile;
129 FAR_FIND_DATA_EX FindData;
130 if (apiGetFindDataEx(strDizFileName, FindData, FIND_FILE_FLAG_CASE_INSENSITIVE) &&
131 DizFile.Open(FindData.strFileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING)) {
132 strDizFileName=FindData.strFileName;
133 GetFileString GetStr(DizFile);
134 wchar_t *DizText;
135 int DizLength;
136 clock_t StartTime = GetProcessUptimeMSec();
137 UINT CodePage = CP_AUTODETECT;
138 bool bSigFound = false;
140 if (!GetFileFormat(DizFile, CodePage, &bSigFound, false) || !bSigFound)
141 CodePage = Opt.Diz.AnsiByDefault ? CP_ACP : CP_OEMCP;
143 while (GetStr.GetString(&DizText, CodePage, DizLength) > 0) {
144 if (!(DizCount & 127) && GetProcessUptimeMSec() - StartTime > 1000) {
145 SetCursorType(FALSE, 0);
146 PR_ReadingMsg();
148 if (CheckForEsc())
149 break;
152 RemoveTrailingSpaces(DizText);
154 if (*DizText)
155 AddRecord(DizText);
158 OrigCodePage = CodePage;
159 Modified = false;
160 DizFile.Close();
161 return;
164 if (DizName)
165 break;
168 Modified = false;
169 strDizFileName.Clear();
172 bool DizList::AddRecord(const wchar_t *DizText)
174 DizRecord *NewDizData = DizData;
176 if (!(DizCount & 15))
177 NewDizData = (DizRecord *)realloc(DizData, (DizCount + 16 + 1) * sizeof(*DizData));
179 if (!NewDizData)
180 return false;
182 DizData = NewDizData;
183 DizData[DizCount].DizLength = StrLength(DizText);
184 DizData[DizCount].DizText = (wchar_t *)malloc((DizData[DizCount].DizLength + 1) * sizeof(wchar_t));
186 if (!DizData[DizCount].DizText)
187 return false;
189 wcscpy(DizData[DizCount].DizText, DizText);
190 DizData[DizCount].NameStart = 0;
191 DizData[DizCount].NameLength = 0;
193 if (*DizText == L'\"') {
194 DizText++;
195 DizData[DizCount].NameStart++;
197 while (*DizText && *DizText != L'\"') {
198 DizText++;
199 DizData[DizCount].NameLength++;
201 } else {
202 while (!IsSpaceOrEos(*DizText)) {
203 DizText++;
204 DizData[DizCount].NameLength++;
208 DizData[DizCount].Deleted = false;
209 NeedRebuild = true;
210 Modified = true;
211 DizCount++;
212 return true;
215 const wchar_t *DizList::GetDizTextAddr(const wchar_t *Name, const int64_t FileSize)
217 const wchar_t *DizText = nullptr;
218 int TextPos;
219 int DizPos = GetDizPosEx(Name, &TextPos);
221 if (DizPos != -1) {
222 DizText = DizData[DizPos].DizText + TextPos;
224 while (*DizText && IsSpace(*DizText))
225 DizText++;
227 if (iswdigit(*DizText)) {
228 wchar_t SizeText[30];
229 const wchar_t *DizPtr = DizText;
230 bool SkipSize = true;
231 swprintf(SizeText, ARRAYSIZE(SizeText), L"%llu", FileSize);
233 for (int I = 0; SizeText[I]; DizPtr++) {
234 if (*DizPtr != L',' && *DizPtr != L'.') {
235 if (SizeText[I++] != *DizPtr) {
236 SkipSize = false;
237 break;
242 if (SkipSize && IsSpace(*DizPtr)) {
243 DizText = DizPtr;
245 while (*DizText && IsSpace(*DizText))
246 DizText++;
251 return DizText;
254 int DizList::GetDizPosEx(const wchar_t *Name, int *TextPos)
256 int DizPos = GetDizPos(Name, TextPos);
258 // если файл описаний был в OEM/ANSI то имена файлов могут не совпадать с юникодными
259 if (DizPos == -1 && !IsUnicodeOrUtfCodePage(OrigCodePage) && OrigCodePage != CP_AUTODETECT) {
260 int len = StrLength(Name);
261 char *tmp = (char *)malloc(len + 1);
263 if (!tmp)
264 return -1;
266 free(AnsiBuf);
267 AnsiBuf = tmp;
268 WINPORT(WideCharToMultiByte)(OrigCodePage, 0, Name, len, AnsiBuf, len, nullptr, nullptr);
269 AnsiBuf[len] = 0;
270 FARString strRecoded(AnsiBuf, OrigCodePage);
272 if (strRecoded == Name)
273 return -1;
275 return GetDizPos(strRecoded, TextPos);
278 return DizPos;
281 int DizList::GetDizPos(const wchar_t *Name, int *TextPos)
283 if (!DizData || !*Name)
284 return -1;
286 if (NeedRebuild)
287 BuildIndex();
289 SearchDizData = DizData;
290 DizSearchKey Key = {Name, StrLength(Name)};
291 int *DestIndex = (int *)bsearch(&Key, IndexData, IndexCount, sizeof(*IndexData), SortDizSearch);
293 if (DestIndex) {
294 if (TextPos) {
295 *TextPos = DizData[*DestIndex].NameStart + DizData[*DestIndex].NameLength;
297 if (DizData[*DestIndex].NameStart && DizData[*DestIndex].DizText[*TextPos] == L'\"')
298 (*TextPos)++;
301 return *DestIndex;
304 return -1;
307 void DizList::BuildIndex()
309 if (!IndexData || IndexCount != DizCount) {
310 if (IndexData)
311 free(IndexData);
313 if (!(IndexData = (int *)malloc(DizCount * sizeof(int)))) {
314 Reset();
315 return;
318 IndexCount = DizCount;
321 for (int I = 0; I < IndexCount; I++)
322 IndexData[I] = I;
324 SearchDizData = DizData;
325 far_qsort((void *)IndexData, IndexCount, sizeof(*IndexData), SortDizIndex);
326 NeedRebuild = false;
329 int _cdecl SortDizIndex(const void *el1, const void *el2)
331 const wchar_t *Diz1 = SearchDizData[*(int *)el1].DizText + SearchDizData[*(int *)el1].NameStart;
332 const wchar_t *Diz2 = SearchDizData[*(int *)el2].DizText + SearchDizData[*(int *)el2].NameStart;
333 int Len1 = SearchDizData[*(int *)el1].NameLength;
334 int Len2 = SearchDizData[*(int *)el2].NameLength;
335 int CmpCode = StrCmpNI(Diz1, Diz2, Min(Len1, Len2));
337 if (!CmpCode) {
338 if (Len1 > Len2)
339 return 1;
341 if (Len1 < Len2)
342 return -1;
344 // for equal names, deleted is bigger
345 bool Del1 = SearchDizData[*(int *)el1].Deleted;
346 bool Del2 = SearchDizData[*(int *)el2].Deleted;
348 if (Del1 && !Del2)
349 return 1;
351 if (Del2 && !Del1)
352 return -1;
355 return CmpCode;
358 int _cdecl SortDizSearch(const void *key, const void *elem)
360 const wchar_t *SearchName = ((DizSearchKey *)key)->Str;
361 wchar_t *DizName = SearchDizData[*(int *)elem].DizText + SearchDizData[*(int *)elem].NameStart;
362 int DizNameLength = SearchDizData[*(int *)elem].NameLength;
363 int NameLength = ((DizSearchKey *)key)->Len;
364 int CmpCode = StrCmpNI(SearchName, DizName, Min(DizNameLength, NameLength));
366 if (!CmpCode) {
367 if (NameLength > DizNameLength)
368 return 1;
370 if (NameLength + 1 < DizNameLength)
371 return -1;
373 // filename == filename.
374 if (NameLength + 1 == DizNameLength
375 && !(DizName[NameLength] == L'.' && wcschr(DizName, L'.') == &DizName[NameLength]))
376 return -1;
378 // for equal names, deleted is bigger so deleted items are never matched
379 if (SearchDizData[*(int *)elem].Deleted)
380 return -1;
383 return CmpCode;
386 bool DizList::DeleteDiz(const wchar_t *Name)
388 int DizPos = GetDizPosEx(Name, nullptr);
390 if (DizPos == -1)
391 return false;
393 DizData[DizPos++].Deleted = true;
395 while (DizPos < DizCount) {
396 if (*DizData[DizPos].DizText && !IsSpace(DizData[DizPos].DizText[0]))
397 break;
399 DizData[DizPos].Deleted = true;
400 DizPos++;
403 Modified = true;
404 NeedRebuild = true;
405 return true;
408 bool DizList::Flush(const wchar_t *Path, const wchar_t *DizName)
410 if (!Modified)
411 return true;
413 if (DizName) {
414 strDizFileName = DizName;
415 } else if (strDizFileName.IsEmpty()) {
416 if (!DizData || !Path)
417 return false;
419 strDizFileName = Path;
420 AddEndSlash(strDizFileName);
421 FARString strArgName;
422 GetCommaWord(Opt.Diz.strListNames, strArgName);
423 strDizFileName+= strArgName;
426 DWORD FileAttr = apiGetFileAttributes(strDizFileName);
428 if (FileAttr != INVALID_FILE_ATTRIBUTES) {
429 if (FileAttr & FILE_ATTRIBUTE_READONLY) {
430 if (Opt.Diz.ROUpdate) {
431 if (apiSetFileAttributes(strDizFileName, FileAttr)) {
432 FileAttr^= FILE_ATTRIBUTE_READONLY;
437 if (!(FileAttr & FILE_ATTRIBUTE_READONLY)) {
438 apiSetFileAttributes(strDizFileName, FILE_ATTRIBUTE_ARCHIVE);
439 } else {
440 Message(MSG_WARNING, 1, Msg::Error, Msg::CannotUpdateDiz, Msg::CannotUpdateRODiz, Msg::Ok);
441 return false;
445 File DizFile;
447 bool AnyError = false;
449 bool EmptyDiz = true;
450 // Don't use CreationDisposition=CREATE_ALWAYS here - it's kills alternate streams
451 if (DizCount
452 && DizFile.Open(strDizFileName, GENERIC_WRITE, FILE_SHARE_READ, nullptr,
453 FileAttr == INVALID_FILE_ATTRIBUTES ? CREATE_NEW : TRUNCATE_EXISTING)) {
454 UINT CodePage = Opt.Diz.SaveInUTF ? CP_UTF8 : (Opt.Diz.AnsiByDefault ? CP_ACP : CP_OEMCP);
456 CachedWrite Cache(DizFile);
458 if (CodePage == CP_UTF8) {
459 DWORD dwSignature = SIGN_UTF8;
460 if (!Cache.Write(&dwSignature, 3)) {
461 AnyError = true;
465 if (!AnyError) {
466 for (int I = 0; I < DizCount; I++) {
467 if (!DizData[I].Deleted) {
468 DWORD Size = (DizData[I].DizLength + 1) * (CodePage == CP_UTF8 ? 3 : 1); // UTF-8, up to 3 bytes per char support
469 char *lpDizText = new (std::nothrow) char[Size];
470 if (lpDizText) {
471 int BytesCount = WINPORT(WideCharToMultiByte)(CodePage, 0, DizData[I].DizText,
472 DizData[I].DizLength + 1, lpDizText, Size, nullptr, nullptr);
473 if (BytesCount && BytesCount - 1) {
474 if (Cache.Write(lpDizText, BytesCount - 1)) {
475 EmptyDiz = false;
476 } else {
477 AnyError = true;
478 break;
480 if (!Cache.Write("\r\n", 2)) {
481 AnyError = true;
482 break;
485 delete[] lpDizText;
491 if (!AnyError) {
492 if (!Cache.Flush()) {
493 AnyError = true;
497 DizFile.Close();
500 if (!EmptyDiz && !AnyError) {
501 if (FileAttr == INVALID_FILE_ATTRIBUTES) {
502 FileAttr = FILE_ATTRIBUTE_ARCHIVE | (Opt.Diz.SetHidden ? FILE_ATTRIBUTE_HIDDEN : 0);
504 apiSetFileAttributes(strDizFileName, FileAttr);
505 } else {
506 apiDeleteFile(strDizFileName);
507 if (AnyError) {
508 Message(MSG_WARNING | MSG_ERRORTYPE, 1, Msg::Error, Msg::CannotUpdateDiz, Msg::Ok);
509 return false;
513 Modified = false;
514 return true;
517 bool DizList::AddDizText(const wchar_t *Name, const wchar_t *DizText)
519 DeleteDiz(Name);
520 FARString strQuotedName = Name;
521 QuoteSpaceOnly(strQuotedName);
522 FormatString FString;
523 FString << fmt::LeftAlign() << fmt::Expand(Opt.Diz.StartPos > 1 ? Opt.Diz.StartPos - 2 : 0)
524 << strQuotedName << L" " << DizText;
525 return AddRecord(FString);
528 bool DizList::CopyDiz(const wchar_t *Name, const wchar_t *DestName, DizList *DestDiz)
530 int TextPos;
531 int DizPos = GetDizPosEx(Name, &TextPos);
533 if (DizPos == -1)
534 return false;
536 while (IsSpace(DizData[DizPos].DizText[TextPos]))
537 TextPos++;
539 DestDiz->AddDizText(DestName, &DizData[DizPos].DizText[TextPos]);
541 while (++DizPos < DizCount) {
542 if (*DizData[DizPos].DizText && !IsSpace(DizData[DizPos].DizText[0]))
543 break;
545 DestDiz->AddRecord(DizData[DizPos].DizText);
548 return true;
551 void DizList::GetDizName(FARString &strDizName)
553 strDizName = strDizFileName;