source: trunk/Packages/Common/UCommon.pas

Last change on this file was 25, checked in by chronos, 21 months ago
  • Modified: CoolTranslator replaced by Common package.
  • Modified: Update common package.
File size: 19.3 KB
Line 
1unit UCommon;
2
3interface
4
5uses
6 {$IFDEF WINDOWS}Windows,{$ENDIF}
7 {$IFDEF UNIX}baseunix,{$ENDIF}
8 Classes, SysUtils, StrUtils, Dialogs, Process, LCLIntf, Graphics,
9 FileUtil, Generics.Collections; //, ShFolder, ShellAPI;
10
11type
12 TArrayOfByte = array of Byte;
13 TExceptionEvent = procedure(Sender: TObject; E: Exception) of object;
14
15 TUserNameFormat = (
16 unfNameUnknown = 0, // Unknown name type.
17 unfNameFullyQualifiedDN = 1, // Fully qualified distinguished name
18 unfNameSamCompatible = 2, // Windows NT® 4.0 account name
19 unfNameDisplay = 3, // A "friendly" display name
20 unfNameUniqueId = 6, // GUID string that the IIDFromString function returns
21 unfNameCanonical = 7, // Complete canonical name
22 unfNameUserPrincipal = 8, // User principal name
23 unfNameCanonicalEx = 9,
24 unfNameServicePrincipal = 10, // Generalized service principal name
25 unfDNSDomainName = 11);
26
27 TFilterMethod = function (FileName: string): Boolean of object;
28 TFileNameMethod = procedure (FileName: string) of object;
29
30var
31 ExceptionHandler: TExceptionEvent;
32 DLLHandle1: HModule;
33
34 {$IFDEF WINDOWS}
35 GetUserNameEx: procedure (NameFormat: DWORD;
36 lpNameBuffer: LPSTR; nSize: PULONG); stdcall;
37 {$ENDIF}
38
39const
40 clLightBlue = TColor($FF8080);
41 clLightGreen = TColor($80FF80);
42 clLightRed = TColor($8080FF);
43
44function AddLeadingZeroes(const aNumber, Length : integer) : string;
45function BinToInt(BinStr: string): Int64;
46function BinToHexString(Source: AnsiString): string;
47//function DelTree(DirName : string): Boolean;
48//function GetSpecialFolderPath(Folder: Integer): string;
49function BCDToInt(Value: Byte): Byte;
50function CompareByteArray(Data1, Data2: TArrayOfByte): Boolean;
51procedure CopyStringArray(Dest: TStringArray; Source: array of string);
52function CombinePaths(Path1, Path2: string): string;
53function ComputerName: string;
54procedure DeleteFiles(APath, AFileSpec: string);
55function Explode(Separator: Char; Data: string): TStringArray;
56procedure ExecuteProgram(Executable: string; Parameters: array of string);
57procedure FileDialogUpdateFilterFileType(FileDialog: TOpenDialog);
58procedure FreeThenNil(var Obj);
59function GetDirCount(Dir: string): Integer;
60function GetUserName: string;
61function GetBitCount(Variable: QWord; MaxIndex: Integer): Integer;
62function GetBit(Variable: QWord; Index: Byte): Boolean;
63function GetStringPart(var Text: string; Separator: string): string;
64function GenerateNewName(OldName: string): string;
65function GetFileFilterItemExt(Filter: string; Index: Integer): string;
66function IntToBin(Data: Int64; Count: Byte): string;
67function Implode(Separator: Char; List: TList<string>): string;
68function LastPos(const SubStr: String; const S: String): Integer;
69function LoadFileToStr(const FileName: TFileName): AnsiString;
70function LoggedOnUserNameEx(Format: TUserNameFormat): string;
71function MergeArray(A, B: array of string): TStringArray;
72function OccurenceOfChar(What: Char; Where: string): Integer;
73procedure OpenWebPage(URL: string);
74procedure OpenEmail(Email: string);
75procedure OpenFileInShell(FileName: string);
76function PosFromIndex(SubStr: string; Text: string;
77 StartIndex: Integer): Integer;
78function PosFromIndexReverse(SubStr: string; Text: string;
79 StartIndex: Integer): Integer;
80function RemoveQuotes(Text: string): string;
81procedure SaveStringToFile(S, FileName: string);
82procedure SetBit(var Variable: Int64; Index: Byte; State: Boolean); overload;
83procedure SetBit(var Variable: QWord; Index: Byte; State: Boolean); overload;
84procedure SetBit(var Variable: Cardinal; Index: Byte; State: Boolean); overload;
85procedure SetBit(var Variable: Word; Index: Byte; State: Boolean); overload;
86procedure SearchFiles(AList: TStrings; Dir: string;
87 FilterMethod: TFilterMethod = nil; FileNameMethod: TFileNameMethod = nil);
88function SplitString(var Text: string; Count: Word): string;
89function StripTags(const S: string): string;
90function TryHexToInt(Data: string; out Value: Integer): Boolean;
91function TryBinToInt(Data: string; out Value: Integer): Boolean;
92procedure SortStrings(Strings: TStrings);
93
94
95implementation
96
97function BinToInt(BinStr : string) : Int64;
98var
99 i : byte;
100 RetVar : Int64;
101begin
102 BinStr := UpperCase(BinStr);
103 if BinStr[length(BinStr)] = 'B' then Delete(BinStr,length(BinStr),1);
104 RetVar := 0;
105 for i := 1 to length(BinStr) do begin
106 if not (BinStr[i] in ['0','1']) then begin
107 RetVar := 0;
108 Break;
109 end;
110 RetVar := (RetVar shl 1) + (byte(BinStr[i]) and 1) ;
111 end;
112
113 Result := RetVar;
114end;
115
116function BinToHexString(Source: AnsiString): string;
117var
118 I: Integer;
119begin
120 Result := '';
121 for I := 1 to Length(Source) do begin
122 Result := Result + LowerCase(IntToHex(Ord(Source[I]), 2));
123 end;
124end;
125
126
127procedure DeleteFiles(APath, AFileSpec: string);
128var
129 SearchRec: TSearchRec;
130 Find: Integer;
131 Path: string;
132begin
133 Path := IncludeTrailingPathDelimiter(APath);
134
135 Find := FindFirst(Path + AFileSpec, faAnyFile xor faDirectory, SearchRec);
136 while Find = 0 do begin
137 DeleteFile(Path + SearchRec.Name);
138
139 Find := SysUtils.FindNext(SearchRec);
140 end;
141 FindClose(SearchRec);
142end;
143
144
145function GetFileFilterItemExt(Filter: string; Index: Integer): string;
146var
147 List: TStringList;
148begin
149 try
150 List := TStringList.Create;
151 List.Text := StringReplace(Filter, '|', #10, [rfReplaceAll]);
152 Result := List[Index * 2 + 1];
153 finally
154 List.Free;
155 end;
156end;
157
158procedure FileDialogUpdateFilterFileType(FileDialog: TOpenDialog);
159var
160 FileExt: string;
161begin
162 FileExt := GetFileFilterItemExt(FileDialog.Filter, FileDialog.FilterIndex - 1);
163 Delete(FileExt, 1, 1); // Remove symbol '*'
164 if FileExt <> '.*' then
165 FileDialog.FileName := ChangeFileExt(FileDialog.FileName, FileExt)
166end;
167
168function GenerateNewName(OldName: string): string;
169var
170 I: Integer;
171 Number: Integer;
172begin
173 Number := 1;
174 // Find number on end
175 if Length(OldName) > 0 then begin
176 I := Length(OldName);
177 while (I > 1) and ((OldName[I] >= '0') and (OldName[I] <= '9')) do Dec(I);
178 TryStrToInt(Copy(OldName, I + 1, Length(OldName) - I), Number);
179 Inc(Number)
180 end;
181 Result := Copy(OldName, 1, I) + IntToStr(Number);
182end;
183
184(*function DelTree(DirName : string): Boolean;
185var
186 SHFileOpStruct : TSHFileOpStruct;
187 DirBuf : array [0..255] of char;
188begin
189 DirName := UTF8Decode(DirName);
190 try
191 Fillchar(SHFileOpStruct,Sizeof(SHFileOpStruct),0) ;
192 FillChar(DirBuf, Sizeof(DirBuf), 0 ) ;
193 StrPCopy(DirBuf, DirName) ;
194 with SHFileOpStruct do begin
195 Wnd := 0;
196 pFrom := @DirBuf;
197 wFunc := FO_DELETE;
198 fFlags := FOF_ALLOWUNDO;
199 fFlags := fFlags or FOF_NOCONFIRMATION;
200 fFlags := fFlags or FOF_SILENT;
201 end;
202 Result := (SHFileOperation(SHFileOpStruct) = 0) ;
203 except
204 Result := False;
205 end;
206end;*)
207
208function LastPos(const SubStr: String; const S: String): Integer;
209begin
210 Result := Pos(ReverseString(SubStr), ReverseString(S));
211 if (Result <> 0) then
212 Result := ((Length(S) - Length(SubStr)) + 1) - Result + 1;
213end;
214
215function BCDToInt(Value: Byte): Byte;
216begin
217 Result := (Value shr 4) * 10 + (Value and 15);
218end;
219
220(*function GetSpecialFolderPath(Folder: Integer): string;
221const
222 SHGFP_TYPE_CURRENT = 0;
223var
224 Path: array[0..MAX_PATH] of Char;
225begin
226 Result := 'C:\Test';
227 if SUCCEEDED(SHGetFolderPath(0, Folder, 0, SHGFP_TYPE_CURRENT, @path[0])) then
228 Result := path
229 else
230 Result := '';
231end;*)
232
233function IntToBin(Data: Int64; Count: Byte): string;
234var
235 I: Integer;
236begin
237 Result := '';
238 for I := 0 to Count - 1 do
239 Result := IntToStr((Data shr I) and 1) + Result;
240end;
241
242function IntToHex(Data: Cardinal; Count: Byte): string;
243const
244 Chars: array[0..15] of Char = '0123456789ABCDEF';
245var
246 I: Integer;
247begin
248 Result := '';
249 for I := 0 to Count - 1 do
250 Result := Result + Chars[(Data shr (I * 4)) and 15];
251end;
252
253function TryHexToInt(Data: string; out Value: Integer): Boolean;
254var
255 I: Integer;
256begin
257 Data := UpperCase(Data);
258 Result := True;
259 Value := 0;
260 for I := 0 to Length(Data) - 1 do begin
261 if (Data[I + 1] >= '0') and (Data[I + 1] <= '9') then
262 Value := Value or (Ord(Data[I + 1]) - Ord('0')) shl ((Length(Data) - I - 1) * 4)
263 else if (Data[I + 1] >= 'A') and (Data[I + 1] <= 'F') then
264 Value := Value or (Ord(Data[I + 1]) - Ord('A') + 10) shl ((Length(Data) - I - 1) * 4)
265 else Result := False;
266 end;
267end;
268
269function TryBinToInt(Data: string; out Value: Integer): Boolean;
270var
271 I: Integer;
272begin
273 Result := True;
274 Value := 0;
275 for I := 0 to Length(Data) - 1 do begin
276 if (Data[I + 1] >= '0') and (Data[I + 1] <= '1') then
277 Value := Value or (Ord(Data[I + 1]) - Ord('0')) shl ((Length(Data) - I - 1))
278 else Result := False;
279 end;
280end;
281
282function CompareByteArray(Data1, Data2: TArrayOfByte): Boolean;
283var
284 I: Integer;
285begin
286 if Length(Data1) = Length(Data2) then begin
287 Result := True;
288 for I := 0 to Length(Data1) - 1 do begin
289 if Data1[I] <> Data2[I] then begin
290 Result := False;
291 Break;
292 end
293 end;
294 end else Result := False;
295end;
296
297function Explode(Separator: Char; Data: string): TStringArray;
298var
299 Index: Integer;
300begin
301 Result := Default(TStringArray);
302 repeat
303 Index := Pos(Separator, Data);
304 if Index > 0 then begin
305 SetLength(Result, Length(Result) + 1);
306 Result[High(Result)] := Copy(Data, 1, Index - 1);
307 Delete(Data, 1, Index);
308 end else Break;
309 until False;
310 if Data <> '' then begin
311 SetLength(Result, Length(Result) + 1);
312 Result[High(Result)] := Data;
313 end;
314end;
315
316function Implode(Separator: Char; List: TList<string>): string;
317var
318 I: Integer;
319begin
320 Result := '';
321 for I := 0 to List.Count - 1 do begin
322 Result := Result + List[I];
323 if I < List.Count - 1 then Result := Result + Separator;
324 end;
325end;
326
327{$IFDEF WINDOWS}
328function GetUserName: string;
329const
330 MAX_USERNAME_LENGTH = 256;
331var
332 L: LongWord;
333begin
334 L := MAX_USERNAME_LENGTH + 2;
335 Result := Default(string);
336 SetLength(Result, L);
337 if Windows.GetUserName(PChar(Result), L) and (L > 0) then begin
338 SetLength(Result, StrLen(PChar(Result)));
339 Result := UTF8Encode(Result);
340 end else Result := '';
341end;
342
343function GetVersionInfo: TOSVersionInfo;
344begin
345 Result.dwOSVersionInfoSize := SizeOf(Result);
346 if GetVersionEx(Result) then begin
347 end;
348end;
349{$ENDIF}
350
351function ComputerName: string;
352{$IFDEF WINDOWS}
353const
354 INFO_BUFFER_SIZE = 32767;
355var
356 Buffer : array[0..INFO_BUFFER_SIZE] of WideChar;
357 Ret : DWORD;
358begin
359 Ret := INFO_BUFFER_SIZE;
360 If (GetComputerNameW(@Buffer[0],Ret)) then begin
361 Result := UTF8Encode(WideString(Buffer));
362 end
363 else begin
364 Result := 'ERROR_NO_COMPUTERNAME_RETURNED';
365 end;
366end;
367{$ENDIF}
368{$IFDEF UNIX}
369var
370 Name: UtsName;
371begin
372 Name := Default(UtsName);
373 fpuname(Name);
374 Result := Name.Nodename;
375end;
376{$ENDIF}
377
378{$IFDEF WINDOWS}
379function LoggedOnUserNameEx(Format: TUserNameFormat): string;
380const
381 MaxLength = 1000;
382var
383 UserName: array[0..MaxLength] of Char;
384 VersionInfo: TOSVersionInfo;
385 Size: DWORD;
386begin
387 VersionInfo := GetVersionInfo;
388 if VersionInfo.dwPlatformId = VER_PLATFORM_WIN32_NT then begin
389 Size := MaxLength;
390 GetUserNameEx(Integer(Format), @UserName, @Size);
391 //ShowMessage(SysErrorMessage(GetLastError));
392 if GetLastError = 0 then Result := UTF8Encode(UserName)
393 else Result := GetUserName;
394 end else Result := GetUserName;
395end;
396{$ELSE}
397function GetUserName: string;
398begin
399 Result := '';
400end;
401
402function LoggedOnUserNameEx(Format: TUserNameFormat): string;
403begin
404 Result := '';
405end;
406
407{$ENDIF}
408
409function SplitString(var Text: string; Count: Word): string;
410begin
411 Result := Copy(Text, 1, Count);
412 Delete(Text, 1, Count);
413end;
414
415function GetBitCount(Variable: QWord; MaxIndex: Integer): Integer;
416var
417 I: Integer;
418begin
419 Result := 0;
420 for I := 0 to MaxIndex - 1 do
421 if ((Variable shr I) and 1) = 1 then Inc(Result);
422end;
423
424function GetBit(Variable:QWord;Index:Byte):Boolean;
425begin
426 Result := ((Variable shr Index) and 1) = 1;
427end;
428
429procedure SetBit(var Variable: Int64; Index: Byte; State: Boolean);
430begin
431 Variable := (Variable and ((1 shl Index) xor High(QWord))) or (Int64(State) shl Index);
432end;
433
434procedure SetBit(var Variable:QWord;Index:Byte;State:Boolean); overload;
435begin
436 Variable := (Variable and ((1 shl Index) xor High(QWord))) or (QWord(State) shl Index);
437end;
438
439procedure SetBit(var Variable:Cardinal;Index:Byte;State:Boolean); overload;
440begin
441 Variable := (Variable and ((1 shl Index) xor High(Cardinal))) or (Cardinal(State) shl Index);
442end;
443
444procedure SetBit(var Variable:Word;Index:Byte;State:Boolean); overload;
445begin
446 Variable := (Variable and ((1 shl Index) xor High(Word))) or (Word(State) shl Index);
447end;
448
449function AddLeadingZeroes(const aNumber, Length : integer) : string;
450begin
451 Result := SysUtils.Format('%.*d', [Length, aNumber]) ;
452end;
453
454procedure LoadLibraries;
455begin
456 {$IFDEF WINDOWS}
457 DLLHandle1 := LoadLibrary('secur32.dll');
458 if DLLHandle1 <> 0 then
459 begin
460 @GetUserNameEx := GetProcAddress(DLLHandle1, 'GetUserNameExA');
461 end;
462 {$ENDIF}
463end;
464
465procedure FreeLibraries;
466begin
467 {$IFDEF WINDOWS}
468 if DLLHandle1 <> 0 then FreeLibrary(DLLHandle1);
469 {$ENDIF}
470end;
471
472procedure ExecuteProgram(Executable: string; Parameters: array of string);
473var
474 Process: TProcess;
475 I: Integer;
476begin
477 try
478 Process := TProcess.Create(nil);
479 Process.Executable := Executable;
480 for I := 0 to Length(Parameters) - 1 do
481 Process.Parameters.Add(Parameters[I]);
482 Process.Options := [poNoConsole];
483 Process.Execute;
484 finally
485 Process.Free;
486 end;
487end;
488
489procedure FreeThenNil(var Obj);
490begin
491 TObject(Obj).Free;
492 TObject(Obj) := nil;
493end;
494
495procedure OpenWebPage(URL: string);
496begin
497 OpenURL(URL);
498end;
499
500procedure OpenEmail(Email: string);
501begin
502 OpenURL('mailto:' + Email);
503end;
504
505procedure OpenFileInShell(FileName: string);
506begin
507 ExecuteProgram('cmd.exe', ['/c', 'start', FileName]);
508end;
509
510function RemoveQuotes(Text: string): string;
511begin
512 Result := Text;
513 if (Pos('"', Text) = 1) and (Text[Length(Text)] = '"') then
514 Result := Copy(Text, 2, Length(Text) - 2);
515end;
516
517function OccurenceOfChar(What: Char; Where: string): Integer;
518var
519 I: Integer;
520begin
521 Result := 0;
522 for I := 1 to Length(Where) do
523 if Where[I] = What then Inc(Result);
524end;
525
526function GetDirCount(Dir: string): Integer;
527begin
528 Result := OccurenceOfChar(DirectorySeparator, Dir);
529 if Copy(Dir, Length(Dir), 1) = DirectorySeparator then
530 Dec(Result);
531end;
532
533function MergeArray(A, B: array of string): TStringArray;
534var
535 I: Integer;
536begin
537 Result := Default(TStringArray);
538 SetLength(Result, Length(A) + Length(B));
539 for I := 0 to Length(A) - 1 do
540 Result[I] := A[I];
541 for I := 0 to Length(B) - 1 do
542 Result[Length(A) + I] := B[I];
543end;
544
545function LoadFileToStr(const FileName: TFileName): AnsiString;
546var
547 FileStream: TFileStream;
548 Read: Integer;
549begin
550 Result := '';
551 FileStream := TFileStream.Create(FileName, fmOpenRead);
552 try
553 if FileStream.Size > 0 then begin
554 SetLength(Result, FileStream.Size);
555 Read := FileStream.Read(Pointer(Result)^, FileStream.Size);
556 SetLength(Result, Read);
557 end;
558 finally
559 FileStream.Free;
560 end;
561end;
562
563function DefaultSearchFilter(const FileName: string): Boolean;
564begin
565 Result := True;
566end;
567
568procedure SaveStringToFile(S, FileName: string);
569var
570 F: TextFile;
571begin
572 AssignFile(F, FileName);
573 try
574 ReWrite(F);
575 Write(F, S);
576 finally
577 CloseFile(F);
578 end;
579end;
580
581procedure SearchFiles(AList: TStrings; Dir: string;
582 FilterMethod: TFilterMethod = nil; FileNameMethod: TFileNameMethod = nil);
583var
584 SR: TSearchRec;
585begin
586 Dir := IncludeTrailingPathDelimiter(Dir);
587 if FindFirst(Dir + '*', faAnyFile, SR) = 0 then
588 try
589 repeat
590 if (SR.Name = '.') or (SR.Name = '..') or (Assigned(FilterMethod) and (not FilterMethod(SR.Name) or
591 not FilterMethod(Copy(Dir, 3, Length(Dir)) + SR.Name))) then Continue;
592 if Assigned(FileNameMethod) then
593 FileNameMethod(Dir + SR.Name);
594 AList.Add(Dir + SR.Name);
595 if (SR.Attr and faDirectory) <> 0 then
596 SearchFiles(AList, Dir + SR.Name, FilterMethod);
597 until FindNext(SR) <> 0;
598 finally
599 FindClose(SR);
600 end;
601end;
602
603function GetStringPart(var Text: string; Separator: string): string;
604var
605 P: Integer;
606begin
607 P := Pos(Separator, Text);
608 if P > 0 then begin
609 Result := Copy(Text, 1, P - 1);
610 Delete(Text, 1, P - 1 + Length(Separator));
611 end else begin
612 Result := Text;
613 Text := '';
614 end;
615 Result := Trim(Result);
616 Text := Trim(Text);
617end;
618
619function StripTags(const S: string): string;
620var
621 Len: Integer;
622
623 function ReadUntil(const ReadFrom: Integer; const C: Char): Integer;
624 var
625 J: Integer;
626 begin
627 for J := ReadFrom to Len do
628 if (S[j] = C) then
629 begin
630 Result := J;
631 Exit;
632 end;
633 Result := Len + 1;
634 end;
635
636var
637 I, APos: Integer;
638begin
639 Len := Length(S);
640 I := 0;
641 Result := '';
642 while (I <= Len) do begin
643 Inc(I);
644 APos := ReadUntil(I, '<');
645 Result := Result + Copy(S, I, APos - i);
646 I := ReadUntil(APos + 1, '>');
647 end;
648end;
649
650function PosFromIndex(SubStr: string; Text: string;
651 StartIndex: Integer): Integer;
652var
653 I, MaxLen: SizeInt;
654 Ptr: PAnsiChar;
655begin
656 Result := 0;
657 if (StartIndex < 1) or (StartIndex > Length(Text) - Length(SubStr)) then Exit;
658 if Length(SubStr) > 0 then begin
659 MaxLen := Length(Text) - Length(SubStr) + 1;
660 I := StartIndex;
661 Ptr := @Text[StartIndex];
662 while (I <= MaxLen) do begin
663 if (SubStr[1] = Ptr^) and (CompareByte(Substr[1], Ptr^, Length(SubStr)) = 0) then begin
664 Result := I;
665 Exit;
666 end;
667 Inc(I);
668 Inc(Ptr);
669 end;
670 end;
671end;
672
673function PosFromIndexReverse(SubStr: string; Text: string;
674 StartIndex: Integer): Integer;
675var
676 I: SizeInt;
677 Ptr: PAnsiChar;
678begin
679 Result := 0;
680 if (StartIndex < 1) or (StartIndex > Length(Text)) then Exit;
681 if Length(SubStr) > 0 then begin
682 I := StartIndex;
683 Ptr := @Text[StartIndex];
684 while (I > 0) do begin
685 if (SubStr[1] = Ptr^) and (CompareByte(Substr[1], Ptr^, Length(SubStr)) = 0) then begin
686 Result := I;
687 Exit;
688 end;
689 Dec(I);
690 Dec(Ptr);
691 end;
692 end;
693end;
694
695procedure CopyStringArray(Dest: TStringArray; Source: array of string);
696var
697 I: Integer;
698begin
699 SetLength(Dest, Length(Source));
700 for I := 0 to Length(Dest) - 1 do
701 Dest[I] := Source[I];
702end;
703
704function CombinePaths(Path1, Path2: string): string;
705begin
706 Result := Path1;
707 if Result <> '' then Result := Result + DirectorySeparator + Path2
708 else Result := Path2;
709end;
710
711procedure SortStrings(Strings: TStrings);
712var
713 Tmp: TStringList;
714begin
715 Strings.BeginUpdate;
716 try
717 if Strings is TStringList then begin
718 TStringList(Strings).Sort;
719 end else begin
720 Tmp := TStringList.Create;
721 try
722 Tmp.Assign(Strings);
723 Tmp.Sort;
724 Strings.Assign(Tmp);
725 finally
726 Tmp.Free;
727 end;
728 end;
729 finally
730 Strings.EndUpdate;
731 end;
732end;
733
734
735initialization
736
737LoadLibraries;
738
739
740finalization
741
742FreeLibraries;
743
744end.
Note: See TracBrowser for help on using the repository browser.