|
|
1.1 root 1: /*
2: Hatari
3:
4: common file access
5: */
6:
7: #include <sys/types.h>
8: #include <sys/stat.h>
9: #include <fcntl.h>
10:
11: #include "main.h"
12: #include "dialog.h"
13: #include "file.h"
14: #include "floppy.h"
15: #include "createBlankImage.h"
16: #include "memAlloc.h"
17: #include "misc.h"
18:
19:
20: //OPENFILENAME ofn;
21: char szSTFilter[256],szROMFilter[256],szAllFilesFilter[256],szMapFileFilter[256],szYMFileFilter[256],szMemoryFileFilter[256];
22: char szCreateDiscFileName[MAX_FILENAME_LENGTH];
23: BOOL bEjectDisc,bCreateBlankDisc;
24:
25: /*-----------------------------------------------------------------------*/
26: /*
27: Initialize Windows 'Open File' dialogs
28: */
29: void File_Init(void)
30: {
31: /* FIXME */
32: /*
33: char chReplace; // string separator for szFilter
34: int i,cbString;
35:
36: // Load '*.ST' filter
37: cbString = LoadString(hInst,IDS_STRING1,szSTFilter,sizeof(szSTFilter));
38: chReplace = szSTFilter[cbString - 1]; // retrieve wildcard
39:
40: for(i=0; szSTFilter[i]!='\0'; i++) {
41: if (szSTFilter[i]==chReplace)
42: szSTFilter[i]='\0';
43: }
44: // Load '*.IMG' filter
45: cbString = LoadString(hInst,IDS_STRING2,szROMFilter,sizeof(szROMFilter));
46: chReplace = szROMFilter[cbString - 1]; // retrieve wildcard
47:
48: for(i=0; szROMFilter[i]!='\0'; i++) {
49: if (szROMFilter[i]==chReplace)
50: szROMFilter[i]='\0';
51: }
52:
53: // Load '*.*' filter
54: cbString = LoadString(hInst,IDS_STRING3,szAllFilesFilter,sizeof(szAllFilesFilter));
55: chReplace = szAllFilesFilter[cbString - 1]; // retrieve wildcard
56:
57: for(i=0; szAllFilesFilter[i]!='\0'; i++) {
58: if (szAllFilesFilter[i]==chReplace)
59: szAllFilesFilter[i]='\0';
60: }
61:
62: // Load '*.map' filter
63: cbString = LoadString(hInst,IDS_STRING4,szMapFileFilter,sizeof(szMapFileFilter));
64: chReplace = szMapFileFilter[cbString - 1]; // retrieve wildcard
65:
66: for(i=0; szMapFileFilter[i]!='\0'; i++) {
67: if (szMapFileFilter[i]==chReplace)
68: szMapFileFilter[i]='\0';
69: }
70:
71: // Load '*.ym' filter
72: cbString = LoadString(hInst,IDS_STRING5,szYMFileFilter,sizeof(szYMFileFilter));
73: chReplace = szYMFileFilter[cbString - 1]; // retrieve wildcard
74:
75: for(i=0; szYMFileFilter[i]!='\0'; i++) {
76: if (szYMFileFilter[i]==chReplace)
77: szYMFileFilter[i]='\0';
78: }
79:
80: // Load '*.mem' filter
81: cbString = LoadString(hInst,IDS_STRING6,szMemoryFileFilter,sizeof(szMemoryFileFilter));
82: chReplace = szMemoryFileFilter[cbString - 1]; // retrieve wildcard
83:
84: for(i=0; szMemoryFileFilter[i]!='\0'; i++) {
85: if (szMemoryFileFilter[i]==chReplace)
86: szMemoryFileFilter[i]='\0';
87: }
88:
89: Memory_Clear(&ofn,sizeof(OPENFILENAME));
90: ofn.lStructSize = sizeof(OPENFILENAME);
91: ofn.hInstance = hInst;
92: ofn.nMaxFile = _MAX_PATH;
93: ofn.nMaxFileTitle = _MAX_FNAME + _MAX_EXT;
94: */
95: }
96:
97:
98: //-----------------------------------------------------------------------
99: /*
100: Create 'Open File' dialog, and ask user for valid filename
101: */
102: BOOL File_OpenDlg(/*HWND hWnd,*/ char *pFullFileName,int Drive)
103: {
104: /* FIXME */
105: /*
106: char szSrcDrive[_MAX_DRIVE],szSrcDir[_MAX_DIR],szSrcName[_MAX_FNAME],szSrcExt[_MAX_EXT];
107: char szTempFileName[MAX_FILENAME_LENGTH];
108: char szTempDir[MAX_FILENAME_LENGTH],szTitleString[64];
109: BOOL bRet;
110:
111: ofn.hwndOwner = hWnd;
112: ofn.lpstrFilter = szSTFilter;
113: ofn.lpstrFileTitle = NULL;
114: // Copy filename as dialog will change this(may not be valid if cancel)
115: strcpy(szTempFileName,pFullFileName);
116: // Create filename and directory of previous file
117: _splitpath(szTempFileName,szSrcDrive,szSrcDir,szSrcName,szSrcExt);
118:
119: // Filename only, save FULL path and file back here when quit
120: _makepath(szTempFileName,"","",szSrcName,szSrcExt);
121: ofn.lpstrFile = szTempFileName;
122: // Directory only
123: _makepath(szTempDir,szSrcDrive,szSrcDir,"","");
124: if (strlen(szTempDir)>0)
125: ofn.lpstrInitialDir = szTempDir;
126: else {
127: File_AddSlashToEndFileName(ConfigureParams.DiscImage.szDiscImageDirectory);
128: ofn.lpstrInitialDir = ConfigureParams.DiscImage.szDiscImageDirectory;
129: }
130: sprintf(szTitleString,"Select Disc Image for Drive '%c'",Drive+'A');
131: ofn.lpstrTitle = szTitleString;
132: ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_NOVALIDATE
133: | OFN_ENABLETEMPLATE | OFN_EXPLORER | OFN_ENABLEHOOK;// | OFN_SHOWHELP;
134: ofn.lpTemplateName = MAKEINTRESOURCE(IDD_DIALOGBAR);
135: ofn.hInstance = hInst;
136: ofn.lpfnHook = File_OpenDlg_OFNHookProc;
137:
138: // Set globals for dialog intercept
139: bEjectDisc = bCreateBlankDisc = FALSE;
140: // Bring up dialog
141: bRet = GetOpenFileName(&ofn);
142: if (bRet)
143: strcpy(pFullFileName,szTempFileName);
144:
145: // Did we eject the disc?
146: if (bEjectDisc) {
147: // Did we have a disc inserted?
148: _splitpath(pFullFileName,szSrcDrive,szSrcDir,szSrcName,szSrcExt);
149: if ( (strlen(szSrcName)>0) || (strlen(szSrcExt)>0) ) {
150: Floppy_EjectDiscFromDrive(Drive,TRUE);
151: }
152: else {
153: MessageBox(hWnd,"There is no disc image selected for that drive - Drive is empty.",PROG_NAME,MB_OK | MB_ICONINFORMATION);
154: Floppy_EjectDiscFromDrive(Drive,FALSE);
155: }
156: // Blank disc filename, and return disc change
157: strcpy(pFullFileName,"");
158: bRet = TRUE;
159: }
160: if (bCreateBlankDisc) {
161: // Do dialog for disc create, filename is in 'szCreateDiscFileName'
162: if (CreateBlankImage_DoDialog(hWnd,Drive,szCreateDiscFileName)) {
163: // Copy filename, so auto-inserts into drive
164: strcpy(pFullFileName,szCreateDiscFileName);
165: }
166: }
167:
168: return(bRet);
169: */
170: return TRUE;
171: }
172:
173: //-----------------------------------------------------------------------
174: /*
175: Create 'Open File' dialog, don't have extra buttons via hook
176: */
177: /*
178: BOOL File_OpenDlg_NoExtraButtons(HWND hWnd, char *pFullFileName)
179: {
180: char szSrcDrive[_MAX_DRIVE],szSrcDir[_MAX_DIR],szSrcName[_MAX_FNAME],szSrcExt[_MAX_EXT];
181: char szTempFileName[MAX_FILENAME_LENGTH];
182: char szTempDir[MAX_FILENAME_LENGTH],szTitleString[64];
183: BOOL bRet;
184:
185: ofn.hwndOwner = hWnd;
186: ofn.lpstrFilter = szSTFilter;
187: ofn.lpstrFileTitle = NULL;
188: // Copy filename as dialog will change this(may not be valid if cancel)
189: strcpy(szTempFileName,pFullFileName);
190: // Create filename and directory of previous file
191: _splitpath(szTempFileName,szSrcDrive,szSrcDir,szSrcName,szSrcExt);
192:
193: // Filename only, save FULL path and file back here when quit
194: _makepath(szTempFileName,"","",szSrcName,szSrcExt);
195: ofn.lpstrFile = szTempFileName;
196: // Directory only
197: _makepath(szTempDir,szSrcDrive,szSrcDir,"","");
198: if (strlen(szTempDir)>0)
199: ofn.lpstrInitialDir = szTempDir;
200: else {
201: File_AddSlashToEndFileName(ConfigureParams.DiscImage.szDiscImageDirectory);
202: ofn.lpstrInitialDir = ConfigureParams.DiscImage.szDiscImageDirectory;
203: }
204: sprintf(szTitleString,"Select Disc Image");
205: ofn.lpstrTitle = szTitleString;
206: ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_NOVALIDATE | OFN_EXPLORER;
207: ofn.lpTemplateName = NULL;
208: ofn.hInstance = hInst;
209: ofn.lpfnHook = File_OpenDlg_OFNHookProc;
210:
211: // Bring up dialog
212: bRet = GetOpenFileName(&ofn);
213: if (bRet)
214: strcpy(pFullFileName,szTempFileName);
215:
216: return(bRet);
217: }
218: */
219:
220: //-----------------------------------------------------------------------
221: /*
222: Create 'Browse' dialog, and ask user for valid directory
223: */
224: /*
225: BOOL File_OpenBrowseDlg(HWND hWnd, char *pFullFileName,BOOL bTosROM,BOOL bFileMustExist)
226: {
227: char szChosenPath[MAX_PATH];
228: BROWSEINFO bInfo;
229: LPITEMIDLIST idList;
230:
231: bInfo.hwndOwner = hWnd;
232: bInfo.pidlRoot = NULL;
233: bInfo.pszDisplayName = szChosenPath;
234: bInfo.lpszTitle = "Select a directory:";
235: bInfo.lpfn = NULL;
236: bInfo.ulFlags = 0;
237: bInfo.lParam = 0;
238: bInfo.iImage = 0;
239:
240: idList = SHBrowseForFolder(&bInfo);
241: if (idList != NULL) {
242: if (SHGetPathFromIDList(idList, szChosenPath)) {
243: strcpy(pFullFileName,szChosenPath);
244: return(TRUE);
245: }
246: }
247:
248: return(FALSE);
249: }
250: */
251:
252: //-----------------------------------------------------------------------
253: /*
254: Create 'Open File' dialog, and ask user for TOS image filename
255: */
256: BOOL File_OpenSelectDlg(/*HWND hWnd,*/ char *pFullFileName,int FileFilter,BOOL bFileMustExist,BOOL bSaving)
257: {
258: /* FIXME */
259: /*
260: char szSrcDrive[_MAX_DRIVE],szSrcDir[_MAX_DIR],szSrcName[_MAX_FNAME],szSrcExt[_MAX_EXT];
261: char szTempDir[MAX_FILENAME_LENGTH];
262:
263: ofn.hwndOwner = hWnd;
264: switch (FileFilter) {
265: case FILEFILTER_DISCFILES:
266: ofn.lpstrFilter = szSTFilter;
267: ofn.lpstrTitle = "Select Disc Image";
268: break;
269: case FILEFILTER_TOSROM:
270: ofn.lpstrFilter = szROMFilter;
271: ofn.lpstrTitle = "Select TOS Image";
272: break;
273: case FILEFILTER_MAPFILE:
274: ofn.lpstrFilter = szMapFileFilter;
275: ofn.lpstrTitle = "Select Keyboard Map file";
276: break;
277: case FILEFILTER_YMFILE:
278: ofn.lpstrFilter = szYMFileFilter;
279: ofn.lpstrTitle = "Select YM or WAV file";
280: break;
281: case FILEFILTER_MEMORYFILE:
282: ofn.lpstrFilter = szMemoryFileFilter;
283: ofn.lpstrTitle = "Select Memory Capture file";
284: break;
285:
286: default:
287: ofn.lpstrFilter = szAllFilesFilter;
288: ofn.lpstrTitle = NULL;
289: break;
290: }
291: ofn.lpstrFileTitle = NULL;
292: // Create filename and directory of previous file
293: _splitpath(pFullFileName,szSrcDrive,szSrcDir,szSrcName,szSrcExt);
294:
295: // Filename only, save FULL path and file back here when quit
296: _makepath(pFullFileName,"","",szSrcName,szSrcExt);
297: ofn.lpstrFile = pFullFileName;
298: // Directory only
299: _makepath(szTempDir,szSrcDrive,szSrcDir,"","");
300: ofn.lpstrInitialDir = szTempDir;
301: if (bFileMustExist)
302: ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
303: else
304: ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
305: ofn.lpTemplateName = MAKEINTRESOURCE(1536);
306:
307: if (bSaving)
308: return GetSaveFileName(&ofn);
309: else
310: return GetOpenFileName(&ofn);
311: */
312: }
313:
314: //-----------------------------------------------------------------------
315: /*
316: Remove any '/'s from end of filenames, but keeps / intact
317: */
318: void File_CleanFileName(char *pszFileName)
319: {
320: char szString[MAX_FILENAME_LENGTH];
321: int i=0,j=0;
322:
323: // Remove end slash from filename! But / remains! Doh!
324: if( strlen(pszFileName)>2 && pszFileName[strlen(pszFileName)-1]=='/' )
325: pszFileName[strlen(pszFileName)-1]=0;
326: }
327:
328: //-----------------------------------------------------------------------
329: /*
330: Add '/' to end of filename
331: */
332: void File_AddSlashToEndFileName(char *pszFileName)
333: {
334: // Check dir/filenames
335: if (strlen(pszFileName)!=0) {
336: if (pszFileName[strlen(pszFileName)-1]!='/')
337: strcat(pszFileName,"/"); // Must use end slash
338: }
339: }
340:
341: /*-----------------------------------------------------------------------*/
342: /*
343: Does filename extension match? If so, return TRUE
344: */
345: BOOL File_DoesFileExtensionMatch(char *pszFileName, char *pszExtension)
346: {
347: if ( strlen(pszFileName) < strlen(pszExtension) )
348: return(FALSE);
349: /* Is matching extension? */
350: if ( !strcasecmp(&pszFileName[strlen(pszFileName)-strlen(pszExtension)], pszExtension) )
351: return(TRUE);
352:
353: /* No */
354: return(FALSE);
355: }
356:
357: //-----------------------------------------------------------------------
358: /*
359: Check if filename is from root
360:
361: Return TRUE if filename is '/', else give FALSE
362: */
363: BOOL File_IsRootFileName(char *pszFileName)
364: {
365: if (pszFileName[0]=='\0') /* If NULL string return! */
366: return(FALSE);
367:
368: if (pszFileName[0]=='/')
369: return(TRUE);
370:
371: return(FALSE);
372: }
373:
374: //-----------------------------------------------------------------------
375: /*
376: Return string, to remove 'C:' part of filename
377: */
378: char *File_RemoveFileNameDrive(char *pszFileName)
379: {
380: if ( (pszFileName[0]!='\0') && (pszFileName[1]==':') )
381: return(&pszFileName[2]);
382: else
383: return(pszFileName);
384: }
385:
386: //-----------------------------------------------------------------------
387: /*
388: Return string, which is just 'C:\' or '\'
389: */
390: char *File_GetFileNameDrive(char *pszFileName)
391: {
392: /* if ( (pszFileName[0]!='\0') && (pszFileName[1]==':') )
393: pszFileName[3] = '\0';*/
394: if (pszFileName[0]=='/')
395: pszFileName[1] = '\0';
396:
397: return(pszFileName);
398: }
399:
400: //-----------------------------------------------------------------------
401: /*
402: Check if filename end with a '/'
403:
404: Return TRUE if filename ends with '/'
405: */
406: BOOL File_DoesFileNameEndWithSlash(char *pszFileName)
407: {
408: if (pszFileName[0]=='\0') /* If NULL string return! */
409: return(FALSE);
410:
411: // Does string end in a '\'
412: if (pszFileName[strlen(pszFileName)-1]=='/')
413: return(TRUE);
414:
415: return(FALSE);
416: }
417:
418: //-----------------------------------------------------------------------
419: /*
420: Remove any double '/'s from end of filenames. So just the one
421: */
422: void File_RemoveFileNameTrailingSlashes(char *pszFileName)
423: {
424: int Length;
425:
426: /* Do have slash at end of filename? */
427: Length = strlen(pszFileName);
428: if (Length>=3) {
429: if (pszFileName[Length-1]=='/') { /* Yes, have one previous? */
430: if (pszFileName[Length-2]=='/')
431: pszFileName[Length-1] = '\0'; /* then remove it! */
432: }
433: }
434: }
435:
436: //-----------------------------------------------------------------------
437: /*
438: Return directory string from full path filename, including trailing '/'
439: */
440: void File_GetDirectoryString(char *pszFileName, char *pszDirName)
441: {
442: fprintf(stderr,"FIXME: File_GetDirectoryString(%s,%s)\n",pszFileName,pszDirName);
443: /* FIXME */
444: /*
445: char szDrive[_MAX_DRIVE],szDir[_MAX_DIR],szName[_MAX_FNAME],szExt[_MAX_EXT];
446:
447: // So, first split name into parts
448: _splitpath(pszFileName,szDrive,szDir,szName,szExt);
449: if (strlen(szExt)>0) {
450: // Recombine, with out filename or extension
451: _makepath(pszDirName,szDrive,szDir,"","");
452: }
453: else {
454: // Was just a directory, so use as is
455: strcpy(pszDirName,pszFileName);
456: }
457: // Make sure ends with a '/'
458: File_AddSlashToEndFileName(pszDirName);
459: */
460: }
461:
462: //-----------------------------------------------------------------------
463: /*
464: Does filename end with a .MSA extension? If so, return TRUE
465: */
466: BOOL File_FileNameIsMSA(char *pszFileName)
467: {
468: return(File_DoesFileExtensionMatch(pszFileName,".msa"));
469: }
470:
471: //-----------------------------------------------------------------------
472: /*
473: Does filename end with a .ST extension? If so, return TRUE
474: */
475: BOOL File_FileNameIsST(char *pszFileName)
476: {
477: return(File_DoesFileExtensionMatch(pszFileName,".st"));
478: }
479:
480:
481:
482: //-----------------------------------------------------------------------
483: /*
484: Read file from PC into memory, allocate memory for it if need to(pass Address as NULL)
485: Also may pass 'unsigned long' if want to find size of file read(may pass as NULL)
486: */
487: void *File_Read(char *pszFileName, void *pAddress, long *pFileSize, char *ppszExts[])
488: {
489: int DiscFile;
490: void *pFile=NULL;
491: long FileSize=0;
492:
493: /* Does the file exist? If not, see if can scan for other extensions and try these */
494: if (!File_Exists(pszFileName) && ppszExts) {
495: /* Try other extensions, if suceeds correct filename is now in 'pszFileName' */
496: File_FindPossibleExtFileName(pszFileName,ppszExts);
497: }
498:
499: /* Open our file */
500: DiscFile = open(pszFileName, O_RDONLY);
501: if (DiscFile>=0) {
502: /* Find size of TOS image - 192k or 256k */
503: FileSize = lseek(DiscFile, 0, SEEK_END);
504: lseek(DiscFile, 0, SEEK_SET);
505: /* Find pointer to where to load, allocate memory if pass NULL */
506: if (pAddress)
507: pFile = pAddress;
508: else
509: pFile = Memory_Alloc(FileSize);
510: /* Read in... */
511: if (pFile)
512: read(DiscFile,(char *)pFile,FileSize);
513:
514: close(DiscFile);
515: }
516: /* Store size of file we read in(or 0 if failed) */
517: if (pFileSize)
518: *pFileSize = FileSize;
519:
520: return(pFile); /* Return to where read in/allocated */
521: }
522:
523: //-----------------------------------------------------------------------
524: /*
525: Save file to PC, return FALSE if errors
526: */
527: BOOL File_Save(char *pszFileName, void *pAddress,long Size,BOOL bQueryOverwrite)
528: {
529: int DiscFile;
530: BOOL bRet=FALSE;
531:
532: /* Check if need to ask user if to overwrite */
533: if (bQueryOverwrite) {
534: /* If file exists, ask if OK to overwrite */
535: if (!File_QueryOverwrite(pszFileName))
536: return(FALSE);
537: }
538:
539: /* Create our file */
540: DiscFile = open(pszFileName, O_CREAT | O_WRONLY);
541: if (DiscFile>=0) {
542: /* Write data, set success flag */
543: if (write(DiscFile,(char *)pAddress,Size)==Size)
544: bRet = TRUE;
545:
546: close(DiscFile);
547: }
548:
549: return(bRet);
550: }
551:
552: //-----------------------------------------------------------------------
553: /*
554: Return size of file, -1 if error
555: */
556: int File_Length(char *pszFileName)
557: {
558: int DiscFile;
559: int FileSize;
560:
561: /* Attempt to open file(with OF_EXIST) */
562: DiscFile = open(pszFileName, O_RDONLY);
563: if (DiscFile>=0) {
564: /* Find length */
565: FileSize = lseek(DiscFile,0,SEEK_END);
566: close(DiscFile);
567:
568: return(FileSize);
569: }
570:
571: return(-1);
572: }
573:
574: //-----------------------------------------------------------------------
575: /*
576: Return TRUE if file exists
577: */
578: BOOL File_Exists(char *pszFileName)
579: {
580: int DiscFile;
581:
582: // Attempt to open file(with OF_EXIST)
583: DiscFile = open(pszFileName, O_RDONLY);
584: if (DiscFile!=-1)
585: return(TRUE);
586: return(FALSE);
587: }
588:
589: //-----------------------------------------------------------------------
590: /*
591: Delete file, return TRUE if OK
592: */
593: BOOL File_Delete(char *pszFileName)
594: {
595: // Delete the file(must be closed first)
596: return( remove(pszFileName) );
597: }
598:
599: //-----------------------------------------------------------------------
600: /*
601: Find if file exists, and if so ask user if OK to overwrite
602: */
603: BOOL File_QueryOverwrite(/*HWND hWnd,*/char *pszFileName)
604: {
605:
606: char szString[MAX_FILENAME_LENGTH];
607:
608: // Try and find if file exists
609: if (File_Exists(pszFileName)) {
610: // File does exist, are we OK to overwrite?
611: sprintf(szString,"File '%s' exists, overwrite?",pszFileName);
612: /* FIXME: */
613: // if (MessageBox(hWnd,szString,PROG_NAME,MB_YESNO | MB_DEFBUTTON2 | MB_ICONSTOP)==IDNO)
614: // return(FALSE);
615: }
616:
617: return(TRUE);
618: }
619:
620: //-----------------------------------------------------------------------
621: /*
622: Try filename with various extensions and check if file exists - if so return correct name
623: */
624: BOOL File_FindPossibleExtFileName(char *pszFileName,char *ppszExts[])
625: {
626: /* FIXME */
627: /*
628: char szSrcDrive[_MAX_DRIVE],szSrcDir[_MAX_DIR],szSrcName[_MAX_FNAME],szSrcExt[_MAX_EXT];
629: char szTempFileName[MAX_FILENAME_LENGTH];
630: int i=0;
631:
632: // Split filename into parts
633: _splitpath(pszFileName,szSrcDrive,szSrcDir,szSrcName,szSrcExt);
634:
635: // Scan possible extensions
636: while(ppszExts[i]) {
637: // Re-build with new file extension
638: _makepath(szTempFileName,szSrcDrive,szSrcDir,szSrcName,ppszExts[i]);
639: // Does this file exist?
640: if (File_Exists(szTempFileName)) {
641: // Copy name for return
642: strcpy(pszFileName,szTempFileName);
643: return(TRUE);
644: }
645:
646: // Next one
647: i++;
648: }
649: */
650: // No, none of the files exist
651: return(FALSE);
652: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.