OSDN Git Service

de6498e7fe6c50a04ac1078d7a5cd5924d4e39eb
[gikonavigoeson/gikonavi.git] / ItemDownload.pas
1 unit ItemDownload;
2
3 interface
4
5 uses
6         Windows, SysUtils, Classes, ComCtrls, Controls, Forms, IdHTTP,
7         {HTTPApp,} YofUtils, IdGlobal, IdException, IdComponent, IniFiles, {DateUtils,}
8         GikoSystem, BoardGroup, MonaUtils, ExternalBoardManager, ExternalBoardPlugInMain,
9         Sort;
10
11 type
12         TDownloadItem = class;
13         TGikoDownloadType = (gdtBoard, gdtThread);
14         TGikoDownloadState = (gdsWait, gdsWork, gdsComplete, gdsDiffComplete, gdsNotModify, gdsAbort, gdsError);
15         TGikoCgiStatus = (gcsOK, gcsINCR, gcsERR);
16         TGikoDLProgress = (gdpStd, gdpAll, gdpDatOchi, gdpOfflaw);
17
18         TGikoWorkEvent = procedure(Sender: TObject; AWorkMode: TWorkMode; const AWorkCount: Integer; ID: Integer) of object;
19         TGikoWorkBeginEvent = procedure(Sender: TObject; AWorkMode: TWorkMode; const AWorkCountMax: Integer; ID: Integer; const AWorkTitle: string) of object;
20         TGikoWorkEndEvent = procedure(Sender: TObject; AWorkMode: TWorkMode; ID: Integer) of object;
21         TDownloadEndEvent = procedure(Sender: TObject; Item: TDownloadItem) of object;
22         TDownloadMsgEvent = procedure(Sender: TObject; Item: TDownloadItem; Msg: string; Icon: TGikoMessageIcon) of object;
23
24         TCgiStatus = record
25                 FStatus: TGikoCgiStatus;
26                 FSize: Integer;
27                 FErrText: string;
28         end;
29
30
31         TDownloadThread = class(TThread)
32         private
33                 FIndy: TIdHttp;
34                 FItem: TDownloadItem;
35                 FNumber: Integer;
36                 FAbort: Boolean;
37                 FMsg: string;
38                 FIcon: TGikoMessageIcon;
39                 FSessionID: string;
40                 FOnWork: TGikoWorkEvent;
41                 FOnWorkBegin: TGikoWorkBeginEvent;
42                 FOnWorkEnd: TGikoWorkEndEvent;
43                 FOnDownloadEnd: TDownloadEndEvent;
44                 FOnDownloadMsg: TDownloadMsgEvent;
45                 FDownloadTitle: string;
46
47                 procedure FireDownloadEnd;
48                 procedure FireDownloadMsg;
49                 procedure GetSessionID;
50                 procedure WorkBegin(Sender: TObject; AWorkMode: TWorkMode; const AWorkCountMax: Integer);
51                 procedure WorkEnd(Sender: TObject; AWorkMode: TWorkMode);
52                 procedure Work(Sender: TObject; AWorkMode: TWorkMode; const AWorkCount: Integer);
53                 function ParseCgiStatus(Content: string): TCgiStatus;
54                 function CgiDownload(ItemType: TGikoDownloadType; URL: string; Modified: TDateTime): Boolean;
55                 function DatDownload(ItemType: TGikoDownloadType; URL: string; Modified: TDateTime; RangeStart: Integer; AdjustLen: Integer): Boolean;
56                 function DeleteStatusLine(Content: string): string;
57         protected
58                 procedure Execute; override;
59         public
60                 property Item: TDownloadItem read FItem write FItem;
61                 property Number: Integer read FNumber write FNumber;
62                 constructor Create(CreateSuspended: Boolean);
63                                 destructor Destroy; override;
64                 procedure Abort;
65                 property OnWork: TGikoWorkEvent read FOnWork write FOnWork;
66                 property OnWorkBegin: TGikoWorkBeginEvent read FOnWorkBegin write FOnWorkBegin;
67                 property OnWorkEnd: TGikoWorkEndEvent read FOnWorkEnd write FOnWorkEnd;
68                 property OnDownloadEnd: TDownloadEndEvent read FOnDownloadEnd write FOnDownloadEnd;
69                 property OnDownloadMsg: TDownloadMsgEvent read FOnDownloadMsg write FOnDownloadMsg;
70         end;
71
72         TDownloadItem = class(TObject)
73         private
74                 FDownType: TGikoDownloadType;
75                 FBoard: TBoard;
76                 FThreadItem: TThreadItem;
77
78                 FContentLength: Integer;
79                 FLastModified: TDateTime;
80                 FContent: string;
81                 FResponseCode: Smallint;
82                 FState: TGikoDownloadState;
83                 FErrText: string;
84                 FForceDownload: Boolean;
85                 FIsAbone : Boolean;
86         public
87                 procedure SaveListFile;
88                 procedure SaveItemFile;
89
90                 property DownType: TGikoDownloadType read FDownType write FDownType;
91                 property Board: TBoard read FBoard write FBoard;
92                 property ThreadItem: TThreadItem read FThreadItem write FThreadItem;
93
94                 property ContentLength: Integer read FContentLength write FContentLength;
95                 property LastModified: TDateTime read FLastModified write FLastModified;
96                 property Content: string read FContent write FContent;
97                 property ResponseCode: Smallint read FResponseCode write FResponseCode;
98                 property State: TGikoDownloadState read FState write FState;
99                 property ErrText: string read FErrText write FErrText;
100                 property ForceDownload: Boolean read FForceDownload write FForceDownload;
101                 property IsAbone : Boolean read FIsAbone write FIsAbone;
102         end;
103
104 implementation
105
106 uses
107         Y_TextConverter;
108
109 constructor TDownloadThread.Create(CreateSuspended: Boolean);
110 begin
111         inherited Create(CreateSuspended);
112         FIndy := TIdHttp.Create(nil);
113
114         FIndy.OnWorkBegin := WorkBegin;
115         FIndy.OnWorkEnd := WorkEnd;
116         FIndy.OnWork := Work;
117 end;
118
119 destructor TDownloadThread.Destroy;
120 begin
121     FIndy.Request.CustomHeaders.Clear;
122     FIndy.Request.RawHeaders.Clear;
123     FIndy.Request.Clear;
124     FIndy.Response.CustomHeaders.Clear;
125     FIndy.Response.RawHeaders.Clear;
126     FIndy.Response.Clear;
127     FIndy.ProxyParams.Clear;
128         FIndy.Free;
129         inherited;
130 end;
131
132 function RFC1123_Date(aDate : TDateTime) : String;
133 const
134          StrWeekDay : String = 'MonTueWedThuFriSatSun';
135          StrMonth        : String = 'JanFebMarAprMayJunJulAugSepOctNovDec';
136 var
137          Year, Month, Day                        : Word;
138          Hour, Min,      Sec, MSec : Word;
139          DayOfWeek                                                      : Word;
140 begin
141          DecodeDate(aDate, Year, Month, Day);
142          DecodeTime(aDate, Hour, Min,    Sec, MSec);
143          DayOfWeek := ((Trunc(aDate) - 2) mod 7);
144          Result := Copy(StrWeekDay, 1 + DayOfWeek * 3, 3) + ', ' +
145                                                  Format('%2.2d %s %4.4d %2.2d:%2.2d:%2.2d',
146                                                                                 [Day, Copy(StrMonth, 1 + 3 * (Month - 1), 3),
147                                                                                  Year, Hour, Min, Sec]);
148 end;
149
150 procedure TDownloadThread.Execute;
151 var
152         ResStream: TMemoryStream;
153
154         URL: string;
155         CgiStatus: TCgiStatus;
156         Modified: TDateTime;
157         RangeStart: Integer;
158         AdjustLen: Integer;
159         Idx: Integer;
160         ATitle: string;
161         DownloadResult: Boolean;
162         boardPlugIn     : TBoardPlugIn;
163         lastContent             : string;
164         logFile                         : TFileStream;
165         adjustMargin    : Integer;
166 const
167         ADJUST_MARGIN   = 16;
168 begin
169         while not Terminated do begin
170                 //===== \83v\83\89\83O\83C\83\93
171                 FAbort := False;
172                 boardPlugIn := nil;
173                 ExternalBoardManager.OnWork                             := Work;
174                 ExternalBoardManager.OnWorkBegin        := WorkBegin;
175                 ExternalBoardManager.OnWorkEnd          := WorkEnd;
176
177                 FDownloadTitle := '';
178                 case FItem.FDownType of
179                 gdtBoard:
180                         begin
181                                 FDownloadTitle := FItem.FBoard.Title;
182                                 if FItem.FBoard <> nil then begin
183                                         if FItem.FBoard.IsBoardPlugInAvailable then begin
184                                                 boardPlugIn     := FItem.FBoard.BoardPlugIn;
185                                                 Item.State      := TGikoDownloadState( boardPlugIn.DownloadBoard( DWORD( FItem.FBoard ) ) );
186                                         end;
187                                 end;
188                         end;
189                 gdtThread:
190                         begin
191                                 FDownloadTitle := FItem.FThreadItem.Title;
192                                 if FItem.FThreadItem <> nil then begin
193                     if FItem.FThreadItem.ParentBoard.IsBoardPlugInAvailable then begin
194                         boardPlugIn := FItem.FThreadItem.ParentBoard.BoardPlugIn;
195                         Item.State      := TGikoDownloadState( boardPlugIn.DownloadThread( DWORD( FItem.FThreadItem ) ) );
196                     end;
197                     //if FItem.FThreadItem.IsBoardPlugInAvailable then begin
198                                         //      boardPlugIn     := FItem.FThreadItem.BoardPlugIn;
199                                         //      Item.State      := TGikoDownloadState( boardPlugIn.DownloadThread( DWORD( FItem.FThreadItem ) ) );
200                                         //end;
201                                 end;
202                         end;
203                 end;
204                 if Length(FDownloadTitle) = 0 then
205                         FDownloadTitle := '\81i\96¼\8fÌ\95s\96¾\81j';
206
207                 if boardPlugIn <> nil then begin
208                         if FAbort then begin
209                                 Item.State := gdsAbort;
210                         end;
211                         if Assigned( OnDownloadEnd ) then
212                                 Synchronize( FireDownloadEnd );
213                         if Terminated then
214                                 Break;
215
216                         Suspend;
217                         Continue;
218                 end;
219
220                 //===== \83v\83\89\83O\83C\83\93\82ð\8eg\97p\82µ\82È\82¢\8fê\8d\87
221                 FAbort := False;
222                 FIndy.Request.CustomHeaders.Clear;
223                 FIndy.Response.Clear;
224                 FIndy.Request.Clear;
225     FIndy.ProxyParams.Clear;
226     FIndy.Disconnect;
227                 FIndy.Request.UserAgent := GikoSys.GetUserAgent;
228                 FIndy.RecvBufferSize := Gikosys.Setting.RecvBufferSize;
229                 FIndy.ProxyParams.BasicAuthentication := False;
230         FIndy.ReadTimeout := GikoSys.Setting.ReadTimeOut;
231                 {$IFDEF DEBUG}
232                 Writeln('------------------------------------------------------------');
233                 {$ENDIF}
234                 //FIndy.AllowCookies := False;
235                 if GikoSys.Setting.ReadProxy then begin
236                         if GikoSys.Setting.ProxyProtocol then
237                                 FIndy.ProtocolVersion := pv1_1
238                         else
239                                 FIndy.ProtocolVersion := pv1_0;
240                         FIndy.ProxyParams.ProxyServer := GikoSys.Setting.ReadProxyAddress;
241                         FIndy.ProxyParams.ProxyPort := GikoSys.Setting.ReadProxyPort;
242                         FIndy.ProxyParams.ProxyUsername := GikoSys.Setting.ReadProxyUserID;
243                         FIndy.ProxyParams.ProxyPassword := GikoSys.Setting.ReadProxyPassword;
244                         if GikoSys.Setting.ReadProxyUserID <> '' then
245                                 FIndy.ProxyParams.BasicAuthentication := True;
246                         {$IFDEF DEBUG}
247                         Writeln('\83v\83\8d\83L\83V\90Ý\92è\82 \82è');
248                         Writeln('\83z\83X\83g: ' + GikoSys.Setting.ReadProxyAddress);
249                         Writeln('\83|\81[\83g: ' + IntToStr(GikoSys.Setting.ReadProxyPort));
250                         {$ENDIF}
251                 end else begin
252                         if GikoSys.Setting.Protocol then
253                                 FIndy.ProtocolVersion := pv1_1
254                         else
255                                 FIndy.ProtocolVersion := pv1_0;
256                         FIndy.ProxyParams.ProxyServer := '';
257                         FIndy.ProxyParams.ProxyPort := 80;
258                         FIndy.ProxyParams.ProxyUsername := '';
259                         FIndy.ProxyParams.ProxyPassword := '';
260                         {$IFDEF DEBUG}
261                         Writeln('\83v\83\8d\83L\83V\90Ý\92è\82È\82µ');
262                         {$ENDIF}
263                 end;
264
265                 adjustMargin := 0;
266                 if Item.DownType = gdtThread then begin
267                         if FileExists( Item.ThreadItem.GetThreadFileName ) then begin
268                                 // dat \83t\83@\83C\83\8b\82Ì\8dÅ\8cã\82ð\93Ç\82Ý\8fo\82·
269                                 SetLength( lastContent, ADJUST_MARGIN + 1 );
270                                 logFile := TFileStream.Create( Item.ThreadItem.GetThreadFileName, fmOpenRead or fmShareDenyWrite );
271                                 try
272                                         logFile.Seek( -(ADJUST_MARGIN + 1), soFromEnd );
273                                         logFile.Read( lastContent[ 1 ], ADJUST_MARGIN + 1 );
274                                         lastContent := StringReplace( lastContent, #13, '', [] );       // CR \82Ì\8dí\8f\9c
275                                 finally
276                                         logFile.Free;
277                                 end;
278                         end else begin
279         lastContent := '';
280                         end;
281                         adjustMargin := Length( lastContent );
282                 end;
283
284                 FIndy.Request.ContentRangeStart := 0;
285                 FIndy.Request.LastModified := ZERO_DATE;
286                 ResStream := TMemoryStream.Create;
287                 try
288                         try
289                                 //********************
290                                 //DAT or Subject\8eæ\93¾
291                                 //********************
292                                 Item.ResponseCode := 0;
293                                 RangeStart := 0;
294                                 AdjustLen := 0;
295                                 Modified := 0;
296                                 if Item.DownType = gdtBoard then begin
297                                         {$IFDEF DEBUG}
298                                         Writeln('Subject\8eæ\93¾');
299                                         Writeln('URL: ' + Item.Board.GetReadCgiURL);
300                                         Writeln('Modified: ' + FloatToStr(Item.Board.LastModified));
301                                         {$ENDIF}
302                                         URL := Item.Board.GetReadCgiURL;
303                                         if Item.ForceDownload then begin
304                                                 // \8b­\90§\8eæ\93¾
305                                                 ATitle := Item.Board.Title;
306                                                 if ATitle = '' then
307                                                         ATitle := '\81i\96¼\8fÌ\95s\96¾\81j';
308                                                 FMsg := '\81\9a\8b­\90§\8eæ\93¾\82ð\8ds\82¢\82Ü\82· - [' + ATitle + ']';
309                                                 FIcon := gmiWhat;
310                                                 if Assigned(OnDownloadMsg) then
311                                                         Synchronize(FireDownloadMsg);
312                                                 Modified := ZERO_DATE
313                                         end else begin
314                                                 Modified := Item.Board.LastModified;
315                                         end;
316                                 end else if Item.DownType = gdtThread then begin
317                                         {$IFDEF DEBUG}
318                                         Writeln('DAT\8eæ\93¾');
319                                         Writeln('URL: ' + Item.ThreadItem.GetDatURL);
320                                         Writeln('Modified: ' + FloatToStr(Item.ThreadItem.LastModified));
321                                         {$ENDIF}
322                                         URL := Item.ThreadItem.GetDatURL;
323                                         if Item.ForceDownload then begin
324                                                 // \8b­\90§\8eæ\93¾
325                                                 ATitle := Item.ThreadItem.Title;
326                                                 if ATitle = '' then
327                                                         ATitle := '\81i\96¼\8fÌ\95s\96¾\81j';
328                                                 FMsg := '\81\9a\8b­\90§\8eæ\93¾\82ð\8ds\82¢\82Ü\82· - [' + ATitle + ']';
329                                                 FIcon := gmiWhat;
330                                                 if FileExists(ChangeFileExt(Item.FThreadItem.GetThreadFileName,'.NG')) = true then
331                                                         DeleteFile(ChangeFileExt(Item.FThreadItem.GetThreadFileName,'.NG'));
332                                                 if Assigned(OnDownloadMsg) then
333                                                         Synchronize(FireDownloadMsg);
334                                                 Modified := ZERO_DATE;
335                                                 RangeStart := 0;
336                                                 AdjustLen := 0;
337                                         end else begin
338                                                 Modified := Item.ThreadItem.LastModified;
339                                                 if Item.ThreadItem.Size > 0 then begin
340                                                         {$IFDEF DEBUG}
341                                                         Writeln('RangeStart: ' + IntToStr(Item.ThreadItem.Size));
342                                                         {$ENDIF}
343                                                         // \82 \82Ú\81[\82ñ\83`\83F\83b\83N\82Ì\82½\82ß adjustMargin \83o\83C\83g\91O\82©\82ç\8eæ\93¾
344                                                         RangeStart := Item.ThreadItem.Size;
345                                                         AdjustLen := -adjustMargin;
346                                                 end;
347                                         end;
348                                 end;
349                                 Item.IsAbone := False;
350                                 DownloadResult := DatDownload(Item.DownType, URL, Modified, RangeStart, AdjustLen);
351                                 {$IFDEF DEBUG}
352                                 Writeln('ResponseCode: ' + IntToStr(FIndy.ResponseCode));
353                                 {$ENDIF}
354                                 if Item.DownType = gdtThread then begin
355                                         if Item.ResponseCode = 416 then begin
356                                                 Item.IsAbone := True;
357                                                 DownloadResult := True;
358                                         end else if DownloadResult and (AdjustLen < 0) then begin
359                                                 if Copy( Item.Content, 1, adjustMargin ) <> lastContent then
360                                                         Item.IsAbone := True;
361                                         end;
362                                 end;
363
364                                 if Trim(FIndy.Response.RawHeaders.Values['Date']) <> '' then begin
365                                         if Item.DownType = gdtBoard then
366                                                 Item.Board.LastGetTime := MonaUtils.DateStrToDateTime(FIndy.Response.RawHeaders.Values['Date'])
367                                         else
368                                                 Item.ThreadItem.ParentBoard.LastGetTime := MonaUtils.DateStrToDateTime(FIndy.Response.RawHeaders.Values['Date']);
369                                 end;
370
371                                 if DownloadResult then begin
372                                         {$IFDEF DEBUG}
373                                         Writeln('Date:' + FIndy.Response.RawHeaders.Values['Date']);
374                                         {$ENDIF}
375                                         if Item.IsAbone then begin
376                                                 {$IFDEF DEBUG}
377                                                 Writeln('\82 \82Ú\81[\82ñ\8c\9f\8fo');
378                                                 {$ENDIF}
379                                                 ATitle := Item.ThreadItem.Title;
380                                                 if ATitle = '' then
381                                                         ATitle := '\81i\96¼\8fÌ\95s\96¾\81j';
382                                                 //\8d·\95ª\8eæ\93¾\82©\82Â\82P\83o\83C\83g\96Ú\82ªLF\82Å\82È\82¢\8fê\8d\87\82Í\81u\82 \82Ú\81[\82ñ\81v\82³\82ê\82Ä\82¢\82é\82©\82à\82µ\82ê\82ñ\82Ì\82Å\8dÄ\8eæ\93¾
383                                                 RangeStart := 0;
384                                                 AdjustLen := 0;
385                                                 FMsg := '\81\9a\81u\82 \82Ú\81[\82ñ\81v\82ð\8c\9f\8fo\82µ\82½\82Ì\82Å\8dÄ\8eæ\93¾\82ð\8ds\82¢\82Ü\82· - [' + ATitle + ']';
386                                                 FIcon := gmiWhat;
387                                                 if FileExists(ChangeFileExt(Item.FThreadItem.GetThreadFileName,'.NG')) = true then
388                                                         DeleteFile(ChangeFileExt(Item.FThreadItem.GetThreadFileName,'.NG'));
389                                                 if Assigned(OnDownloadMsg) then
390                                                         Synchronize(FireDownloadMsg);
391                                                 if not DatDownload(Item.DownType, URL, ZERO_DATE, RangeStart, AdjustLen) then
392                                                         Item.State := gdsError;
393                                                 {$IFDEF DEBUG}
394                                                 Writeln('\82 \82Ú\81[\82ñ\8dÄ\8eæ\93¾\8cã');
395                                                 Writeln('ResponseCode: ' + IntToStr(Item.ResponseCode));
396                                                 {$ENDIF}
397                                         end else if (Item.DownType = gdtThread) and (AdjustLen < 0) then begin
398                                                 // \8d·\95ª\8eæ\93¾\82ª\8fo\97\88\82½\8fê\8d\87\82Í\82 \82Ú\81[\82ñ\83`\83F\83b\83N\97p\82É\8eæ\93¾\82µ\82½\97]\95ª\82È\83T\83C\83Y\82ð\8dí\8f\9c
399                                                 Item.Content := Copy(Item.Content, adjustMargin + 1, MaxInt);
400                                         end;
401                                 end else begin
402                                         Item.State := gdsError;
403                                         if (Item.DownType = gdtBoard) and (Item.ResponseCode = 302) then begin
404                                                 FMsg := '\81\9a\81\9a\94Â\82ª\88Ú\93]\82µ\82½\82©\82à\82µ\82ê\82È\82¢\82Ì\82Å\94Â\8dX\90V\82ð\8ds\82Á\82Ä\82­\82¾\82³\82¢\81\9a\81\9a';
405                                                 FIcon := gmiNG;
406                                                 if Assigned(OnDownloadMsg) then
407                                                         Synchronize(FireDownloadMsg);
408                                         end;
409                                 end;
410
411                                 //********************
412                                 //dat.gz\8eæ\93¾(1)
413                                 //********************
414                                 if (Item.DownType = gdtThread) and (Item.ResponseCode = 302) then begin
415                                         {$IFDEF DEBUG}
416                                         Writeln('dat.gz\8eæ\93¾');
417                                         {$ENDIF}
418                                         ATitle := Item.ThreadItem.Title;
419                                         if ATitle = '' then
420                                                 ATitle := '\81i\96¼\8fÌ\95s\96¾\81j';
421                                         FMsg := '\81\9adat\82ª\91\8dÝ\82µ\82È\82¢\82½\82ß\89ß\8b\8e\83\8d\83O(dat.gz)\82ð\92T\82µ\82Ü\82· - [' + ATitle + ']';
422                                         FIcon := gmiWhat;
423                                         if Assigned(OnDownloadMsg) then
424                                                 Synchronize(FireDownloadMsg);
425                                         URL := Item.ThreadItem.GetDatgzURL;
426                                         Modified := Item.ThreadItem.LastModified;
427                                         RangeStart := 0;
428                                         AdjustLen := 0;
429                                         if not DatDownload(Item.DownType, URL, Modified, RangeStart, AdjustLen) then
430                                                 Item.State := gdsError;
431                                         {$IFDEF DEBUG}
432                                         Writeln('ResponseCode: ' + IntToStr(Item.ResponseCode));
433                                         {$ENDIF}
434                                 end;
435
436                                 //********************
437                                 //dat.gz\8eæ\93¾(2)
438                                 //********************
439 {
440                                 if (Item.DownType = gdtThread) and (Item.ResponseCode = 302) then begin
441                                         ATitle := Item.ThreadItem.Title;
442                                         if ATitle = '' then
443                                                 ATitle := '\81i\96¼\8fÌ\95s\96¾\81j';
444                                         FMsg := '\81\9a\89ß\8b\8e\83\8d\83O(1)\82ª\91\8dÝ\82µ\82È\82¢\82½\82ß\89ß\8b\8e\83\8d\83O(2)\82©\82ç\92T\82µ\82Ü\82· - [' + ATitle + ']';
445                                         FIcon := gmiWhat;
446                                         if Assigned(OnDownloadMsg) then
447                                                 Synchronize(FireDownloadMsg);
448                                         URL := Item.ThreadItem.GetOldDatgzURL;
449                                         Modified := Item.ThreadItem.LastModified;
450                                         RangeStart := 0;
451                                         AdjustLen := 0;
452                                         if not DatDownload(Item.DownType, URL, Modified, RangeStart, AdjustLen) then
453                                                 Item.State := gdsError;
454                                 end;
455 }
456
457                                 //********************
458                                 //offlaw.cgi\82Å\8eæ\93¾
459                                 //********************
460                                 FSessionID := '';
461                                 Synchronize(GetSessionID);
462                                 if (Item.DownType = gdtThread) and (Item.ResponseCode = 302) and (FSessionID <> '') then begin
463                                         {$IFDEF DEBUG}
464                                         Writeln('offlaw.cgi\82Å\8eæ\93¾');
465                                         {$ENDIF}
466                                         ATitle := Item.ThreadItem.Title;
467                                         if ATitle = '' then
468                                                 ATitle := '\81i\96¼\8fÌ\95s\96¾\81j';
469                                         FMsg := '\81\9adat.gz\82ª\91\8dÝ\82µ\82È\82¢\82½\82ßofflaw.cgi\82ð\97\98\97p\82µ\82Ü\82· - [' + ATitle + ']';
470                                         FIcon := gmiWhat;
471                                         if Assigned(OnDownloadMsg) then
472                                                 Synchronize(FireDownloadMsg);
473                                         URL := Item.ThreadItem.GetOfflawCgiURL(FSessionID);
474                                         Modified := Item.ThreadItem.LastModified;
475                                         RangeStart := 0;
476                                         AdjustLen := 0;
477                                         if not DatDownload(Item.DownType, URL, Modified, RangeStart, AdjustLen) then begin
478                                                 {$IFDEF DEBUG}
479                                                 Writeln('ResponseCode: ' + IntToStr(Item.ResponseCode));
480                                                 {$ENDIF}
481                                                 Item.State := gdsError;
482
483                                                 if (Item.DownType = gdtThread) and (Item.ResponseCode = 302) then begin
484                                                         FMsg := '\94Â\82ª\88Ú\93]\82µ\82½\82©\82à\82µ\82ê\82È\82¢\82Ì\82Å\94Â\8dX\90V\82ð\8ds\82Á\82Ä\82­\82¾\82³\82¢\81B';
485                                                         FIcon := gmiNG;
486                                                         if Assigned(OnDownloadMsg) then
487                                                                 Synchronize(FireDownloadMsg);
488                                                 end;
489
490                                         end else begin
491                                                 CgiStatus := ParseCgiStatus(Item.Content);
492                                                 {$IFDEF DEBUG}
493                                                 Writeln('ResponseCode: ' + IntToStr(Item.ResponseCode));
494                                                 {$ENDIF}
495                                                 case CgiStatus.FStatus of
496                                                         gcsOK: begin
497                                                                 {$IFDEF DEBUG}
498                                                                 Writeln('CGIStatus: OK');
499                                                                 {$ENDIF}
500                                                                 Item.ResponseCode := 200;
501                                                                 Item.Content := DeleteStatusLine(Item.Content);
502                                                                 Item.ContentLength := CgiStatus.FSize;
503                                                         end;
504                                                         gcsINCR: begin
505                                                                 //\8d¡\82Í\82 \82è\82¦\82È\82¢
506                                                                 {$IFDEF DEBUG}
507                                                                 Writeln('CGIStatus: 206');
508                                                                 {$ENDIF}
509                                                                 Item.ResponseCode := 206;
510                                                                 Item.Content := DeleteStatusLine(Item.Content);
511                                                                 Item.ContentLength := CgiStatus.FSize;
512                                                         end;
513                                                         gcsERR: begin
514                                                                 {$IFDEF DEBUG}
515                                                                 Writeln('CGIStatus: 404(ERROR)');
516                                                                 {$ENDIF}
517                                                                 Item.ResponseCode := 404;
518                                                                 Item.State := gdsError;
519                                                                 Item.ErrText := CgiStatus.FErrText;
520                                                         end;
521                                                 end;
522                                                 if (Item.ResponseCode = 404) and (AnsiPos('\89ß\8b\8e\83\8d\83O\91q\8cÉ\82Å\94­\8c©', Item.ErrText) <> 0) then begin
523                                                         {$IFDEF DEBUG}
524                                                         Writeln('\89ß\8b\8e\83\8d\83O\8eæ\93¾');
525                                                         {$ENDIF}
526                                                         ATitle := Item.ThreadItem.Title;
527                                                         if ATitle = '' then
528                                                                 ATitle := '\81i\96¼\8fÌ\95s\96¾\81j';
529                                                         FMsg := '\81\9a\89ß\8b\8e\83\8d\83O\91q\8cÉ\82Å\94­\8c© - [' + ATitle + ']';
530                                                         FIcon := gmiWhat;
531                                                         if Assigned(OnDownloadMsg) then
532                                                                 Synchronize(FireDownloadMsg);
533                                                         Idx := Pos(' ', Item.ErrText);
534                                                         if Idx <> 0 then begin
535                                                                 URL := Copy(Item.ErrText, Idx + 1, Length(Item.ErrText));
536                                                                 if Pos( '://', URL ) = 0 then begin
537                                                                         if Pos('../', URL) = 1 then
538                                                                                 URL := Copy(URL, 4, MaxInt );
539                                                                         if Pos( '/', URL ) = 1 then
540                                                                                 URL := Copy( URL, 2, MaxInt );
541                                                                         URL := GikoSys.UrlToServer(Item.ThreadItem.ParentBoard.URL) + URL;
542                                                                 end;
543                                                                 Modified := Item.ThreadItem.LastModified;
544                                                                 RangeStart := 0;
545                                                                 AdjustLen := 0;
546                                                                 if not DatDownload(Item.DownType, URL, Modified, RangeStart, AdjustLen) then
547                                                                         Item.State := gdsError;
548                                                                 {$IFDEF DEBUG}
549                                                                 Writeln('ResponseCode: ' + IntToStr(Item.ResponseCode));
550                                                                 {$ENDIF}
551                                                         end;
552                                                 end;
553                                         end;
554                                 end else begin
555                                         if (Item.DownType = gdtThread) and (Item.ResponseCode = 302) and (FSessionID = '') then begin
556                                                 {$IFDEF DEBUG}
557                                                 Writeln('\83\8d\83O\83C\83\93\82³\82ê\82Ä\82È\82¢\82Ì\82Å\89ß\8b\8e\83\8d\83O\8eæ\93¾\95s\89Â');
558                                                 {$ENDIF}
559                                                 ATitle := Item.ThreadItem.Title;
560                                                 if ATitle = '' then
561                                                         ATitle := '\81i\96¼\8fÌ\95s\96¾\81j';
562                                                 FMsg := '\81\9a\83\8d\83O\83C\83\93\82³\82ê\82Ä\82¢\82È\82¢\82½\82ß\92T\82·\82±\82Æ\82ª\8fo\97\88\82Ü\82¹\82ñ - [' + ATitle + ']';
563                                                 FIcon := gmiSAD;
564                                                 if Assigned(OnDownloadMsg) then
565                                                         Synchronize(FireDownloadMsg);
566                                         end;
567                                 end;
568
569                                 case Item.ResponseCode of
570                                         200: Item.State := gdsComplete;
571                                         206: Item.State := gdsDiffComplete;
572                                         304: Item.State := gdsNotModify;
573                                         else
574                                                 Item.State := gdsError;
575                                 end;
576 {
577                                 //\96³\82¢\82Æ\8ev\82¤\82¯\82Ç\81B\81B\81B
578                                 if (Item.ResponseCode in [200, 206]) and (Item.Content = '') then
579                                         Item.State := gdsError;
580                                 Item.LastModified := FIndy.Response.LastModified;
581                                 //\8d·\95ª\8eæ\93¾\82Å\82P\83o\83C\83g\91O\82©\82ç\82Æ\82Á\82Ä\82«\82½\82Æ\82«\82Í\83}\83C\83i\83X\82·\82é
582                                 Item.ContentLength := FIndy.Response.ContentLength + AdjustLen;
583                                 try
584                                         ResStream.Clear;
585                                         FIndy.Get(URL, ResStream);
586                                         Item.Content := GikoSys.GzipDecompress(ResStream, FIndy.Response.ContentEncoding);
587                                         if (Item.DownType = gdtThread) and (AdjustLen = -1) and (Item.Content[1] <> #10) then begin
588                                                 //\8d·\95ª\8eæ\93¾\82©\82Â\82P\83o\83C\83g\96Ú\82ªLF\82Å\82È\82¢\8fê\8d\87\82Í\81u\82 \82Ú\81[\82ñ\81v\82³\82ê\82Ä\82¢\82é\82©\82à\82µ\82ê\82ñ\82Ì\82Å\8dÄ\8eæ\93¾
589                                                 //\82±\82±\82Å\83\81\83b\83Z\81[\83W\95\\8e¦\83C\83x\83\93\83g
590                                                 //event
591                                                 FMsg := '\81u\82 \82Ú\81[\82ñ\81v\82ð\8c\9f\8fo\82µ\82½\82Ì\82Å\8dÄ\8eæ\93¾\82ð\8ds\82¢\82Ü\82·';
592                                                 if Assigned(OnDownloadMsg) then
593                                                         Synchronize(FireDownloadMsg);
594                                                 FIndy.Request.ContentRangeStart := 0;
595                                                 FIndy.Request.ContentRangeEnd := 0;
596                                                 AdjustLen := 0;
597                                                 ResStream.Clear;
598                                                 FIndy.Get(URL, ResStream);
599                                                 Item.Content := GikoSys.GzipDecompress(ResStream, FIndy.Response.ContentEncoding);
600                                         end else if (Item.DownType = gdtThread) and (AdjustLen = -1) and (Item.Content[1] = #10) then begin
601                                                 //\8d·\95ª\8eæ\93¾\82©\82Â\82P\83o\83C\83g\96Ú\82ªLF\82Ì\8fê\8d\87\81i\90³\8fí\8eæ\93¾\81j\82Í\93ª\82ÌLF\82ð\8dí\8f\9c
602                                                 Item.Content := Copy(Item.Content, 2, Length(Item.Content));
603                                         end;
604                                 except
605                                         Item.State := gdsError;
606                                 end;
607                                 Item.ResponseCode := FIndy.ResponseCode;
608 }
609 {
610                                 try
611                                         ResStream.Clear;
612                                         FIndy.Get(URL, ResStream);
613                                         Item.Content := GikoSys.GzipDecompress(ResStream, FIndy.Response.ContentEncoding);
614                                 except
615                                         Item.State := gdsError;
616                                 end;
617
618                                 CgiStatus := ParseCgiStatus(Item.Content);
619                                 if CgiStatus.FStatus = gcsOK then begin
620                                         if CgiStatus.FSize = 0 then
621                                                 Item.State := gdsNotModify
622                                         else if Item.DownType = gdtBoard then
623                                                 Item.State := gdsComplete
624                                         else
625                                                 Item.State := gdsDiffComplete;
626                                 end else if CgiStatus.FStatus = gcsINCR then begin
627                                         Item.State := gdsComplete;
628                                 end else if CgiStatus.FStatus = gcsERR then begin
629                                         Item.State := gdsError;
630                                         Item.ErrText := CgiStatus.FErrText;
631                                 end;
632                                 Item.ContentLength := CgiStatus.FSize;
633                                 }
634                         except
635                                 Item.State := gdsError;
636                         end;
637                         //Item.ResponseCode := FIndy.ResponseCode;
638                         if FAbort then
639                                 Item.State := gdsAbort;
640                 finally
641                         if Assigned(OnDownloadEnd) then
642                                 Synchronize(FireDownloadEnd);
643                         ResStream.Free;
644                 end;
645
646                 FIndy.Request.CustomHeaders.Clear;
647         FIndy.Request.RawHeaders.Clear;
648         FIndy.Request.Clear;
649         FIndy.Response.CustomHeaders.Clear;
650         FIndy.Response.RawHeaders.Clear;
651                 FIndy.Response.Clear;
652             FIndy.ProxyParams.Clear;
653
654                 if Terminated then Break;
655                 Suspend;
656         end;
657 end;
658
659 function TDownloadThread.CgiDownload(ItemType: TGikoDownloadType; URL: string; Modified: TDateTime): Boolean;
660 var
661         ResponseCode: Integer;
662         ResStream: TMemoryStream;
663 begin
664         ResponseCode := -1;
665         FIndy.Request.ContentRangeStart := 0;
666         FIndy.Request.ContentRangeEnd := 0;
667
668         FIndy.Request.CustomHeaders.Clear;
669         if (Modified <> 0) and (Modified <> ZERO_DATE) then begin
670                 FIndy.Request.LastModified := modified - OffsetFromUTC;
671         end;
672         FIndy.Request.AcceptEncoding := '';
673         FIndy.Request.Accept := 'text/html';
674         ResStream := TMemoryStream.Create;
675         try
676                 try
677                         ResStream.Clear;
678                         {$IFDEF DEBUG}
679                         Writeln('URL: ' + URL);
680                         {$ENDIF}
681                         FIndy.Get(URL, ResStream);
682                         Item.Content := GikoSys.GzipDecompress(ResStream, FIndy.Response.ContentEncoding);
683                         Item.LastModified := FIndy.Response.LastModified;
684                         //\8d·\95ª\8eæ\93¾\82Å\82P\83o\83C\83g\91O\82©\82ç\82Æ\82Á\82Ä\82«\82½\82Æ\82«\82Í\83}\83C\83i\83X\82·\82é
685                         Item.ContentLength := Length(Item.Content);
686                         //\96³\82¢\82Æ\8ev\82¤\82¯\82Ç\81B\81B\81B
687                         if Item.Content = '' then
688                                 Result := False
689                         else
690                                 Result := True;
691                         {$IFDEF DEBUG}
692                         Writeln('\8eæ\93¾\82Å\97á\8aO\82È\82µ');
693                         {$ENDIF}
694                         ResponseCode := FIndy.ResponseCode;
695                 except
696                         on E: EIdSocketError do begin
697                                 Item.Content := '';
698                                 Item.LastModified := ZERO_DATE;
699                                 Item.ContentLength := 0;
700                                 Item.ErrText := E.Message;
701                                 Result := False;
702                                 ResponseCode := -1;
703                                 Screen.Cursor := crDefault;
704                         end;
705                         on E: EIdConnectException do begin
706                                 Item.Content := '';
707                                 Item.LastModified := ZERO_DATE;
708                                 Item.ContentLength := 0;
709                                 Item.ErrText := E.Message;
710                                 Result := False;
711                                 ResponseCode := -1;
712                                 Screen.Cursor := crDefault;
713                         end;
714                         on E: Exception do begin
715                                 {$IFDEF DEBUG}
716                                 Writeln('\8eæ\93¾\82Å\97á\8aO\82 \82è');
717                                 Writeln('E.Message: ' + E.Message);
718                                 {$ENDIF}
719                                 Item.Content := '';
720                                 Item.LastModified := ZERO_DATE;
721                                 Item.ContentLength := 0;
722                                 Item.ErrText := E.Message;
723                                 ResponseCode := FIndy.ResponseCode;
724                                 Result := False;
725                                 Screen.Cursor := crDefault;
726                         end;
727                 end;
728         finally
729                 if (Item.ContentLength = 0) and (ResponseCode = 206) then
730                         Item.ResponseCode := 304
731                 else
732                         Item.ResponseCode := ResponseCode;
733                 ResStream.Free;
734         end;
735 end;
736
737 function TDownloadThread.DatDownload(ItemType: TGikoDownloadType; URL: string; Modified: TDateTime; RangeStart: Integer; AdjustLen: Integer): Boolean;
738 var
739         ResponseCode: Integer;
740         ResStream: TMemoryStream;
741 begin
742         ResponseCode := -1;
743         if (ItemType = gdtThread) and (RangeStart > 0) then begin
744 //      if (ItemType = gdtThread) and (Item.ThreadItem.Size > 0) then begin
745 //              FIndy.Request.ContentRangeStart := Item.ThreadItem.Size + AdjustLen;
746                 FIndy.Request.ContentRangeStart := RangeStart + AdjustLen;
747                 FIndy.Request.ContentRangeEnd := 0;
748         end else begin
749                 FIndy.Request.ContentRangeStart := 0;
750                 FIndy.Request.ContentRangeEnd := 0;
751         end;
752
753         FIndy.Request.CustomHeaders.Clear;
754         FIndy.Request.CacheControl := 'no-cache';
755         FIndy.Request.CustomHeaders.Add('Pragma: no-cache');
756         if (Modified <> 0) and (Modified <> ZERO_DATE) then begin
757                 FIndy.Request.LastModified := modified - OffsetFromUTC;
758                 //FIndy.Request.CustomHeaders.Add('If-Modified-Since: ' + RFC1123_Date(modified - OffsetFromUTC) + ' GMT');
759         end;
760 //      FIndy.Request.AcceptEncoding := 'gzip';
761         if RangeStart = 0 then
762                 FIndy.Request.AcceptEncoding := 'gzip'
763         else
764                 FIndy.Request.AcceptEncoding := '';
765         ResStream := TMemoryStream.Create;
766         try
767                 try
768                         ResStream.Clear;
769                         {$IFDEF DEBUG}
770                         Writeln('URL: ' + URL);
771                         {$ENDIF}
772                         FIndy.Get(URL, ResStream);
773             Item.Content := GikoSys.GzipDecompress(ResStream, FIndy.Response.ContentEncoding);
774                         {$IFDEF DEBUG}
775                         if (FIndy.URL.Host = 'be.2ch.net') and (FIndy.URL.Path = '/be/') then begin
776                                 Item.Content := EUCtoSJIS(Item.Content);
777                         end;
778             {$ENDIF}
779                         Item.LastModified := FIndy.Response.LastModified;
780                         //\8d·\95ª\8eæ\93¾\82Å\82P\83o\83C\83g\91O\82©\82ç\82Æ\82Á\82Ä\82«\82½\82Æ\82«\82Í\83}\83C\83i\83X\82·\82é
781 //                      Item.ContentLength := FIndy.Response.ContentLength + AdjustLen;
782                         Item.ContentLength := Length(Item.Content) + AdjustLen;
783                         //\96³\82¢\82Æ\8ev\82¤\82¯\82Ç\81B\81B\81B
784 //                      if (FIndy.ResponseCode in [200, 206]) and (Item.Content = '') then
785 //                              Result := False
786                         if Item.Content = '' then
787                                 Result := False
788                         else
789                                 Result := True;
790                         {$IFDEF DEBUG}
791                         Writeln('\8eæ\93¾\82Å\97á\8aO\82È\82µ');
792                         {$ENDIF}
793                         ResponseCode := FIndy.ResponseCode;
794                 except
795                         on E: EIdSocketError do begin
796                                 Item.Content := '';
797                                 Item.LastModified := ZERO_DATE;
798                                 Item.ContentLength := 0;
799                                 Item.ErrText := E.Message;
800                                 Result := False;
801                                 ResponseCode := -1;
802                                 Screen.Cursor := crDefault;
803                         end;
804                         on E: EIdConnectException do begin
805                                 Item.Content := '';
806                                 Item.LastModified := ZERO_DATE;
807                                 Item.ContentLength := 0;
808                                 Item.ErrText := E.Message;
809                                 Result := False;
810                                 ResponseCode := -1;
811                                 Screen.Cursor := crDefault;
812                         end;
813                         on E: Exception do begin
814                                 {$IFDEF DEBUG}
815                                 Writeln('\8eæ\93¾\82Å\97á\8aO\82 \82è');
816                                 Writeln('E.Message: ' + E.Message);
817                                 {$ENDIF}
818                                 Item.Content := '';
819                                 Item.LastModified := ZERO_DATE;
820                                 Item.ContentLength := 0;
821                                 Item.ErrText := E.Message;
822                                 ResponseCode := FIndy.ResponseCode;
823                                 Result := False;
824                                 Screen.Cursor := crDefault;
825                         end;
826                 end;
827         finally
828                 if (Item.ContentLength = 0) and (ResponseCode = 206) then
829                         Item.ResponseCode := 304
830                 else
831                         Item.ResponseCode := ResponseCode;
832                 ResStream.Free;
833         end;
834 end;
835
836 procedure TDownloadThread.FireDownloadEnd;
837 begin
838         OnDownloadEnd(self, Item);
839 end;
840
841 procedure TDownloadThread.FireDownloadMsg;
842 begin
843         OnDownloadMsg(Self, Item, FMsg, FIcon);
844 end;
845
846 procedure TDownloadThread.GetSessionID;
847 begin
848         FSessionID := GikoSys.Dolib.SessionID;
849 end;
850
851 procedure TDownloadThread.Abort;
852 begin
853         FAbort := True;
854         FIndy.DisconnectSocket;
855         if socket <> nil then begin
856                 socket.DisconnectSocket;
857         end;
858 end;
859
860 procedure TDownloadThread.WorkBegin(Sender: TObject; AWorkMode: TWorkMode; const AWorkCountMax: Integer);
861 begin
862         if Assigned(OnWorkBegin) then
863                 OnWorkBegin(Sender, AWorkMode, AWorkCountMax, FNumber, FDownloadTitle);
864 end;
865
866 procedure TDownloadThread.WorkEnd(Sender: TObject; AWorkMode: TWorkMode);
867 begin
868         if Assigned(OnWorkEnd) then
869                 OnWorkEnd(Sender, AWorkMode, FNumber);
870 end;
871
872 procedure TDownloadThread.Work(Sender: TObject; AWorkMode: TWorkMode; const AWorkCount: Integer);
873 begin
874         if Assigned(OnWork) then
875                 OnWork(Sender, AWorkMode, AWorkCount, FNumber);
876 end;
877
878 function TDownloadThread.ParseCgiStatus(Content: string): TCgiStatus;
879 var
880         StatusLine: string;
881         Line: string;
882         Idx: Integer;
883         Status: string;
884         Size: string;
885         Msg: string;
886 begin
887 // [+OK] \82Ì\8fê\8d\87\82Í\8d·\95ª
888 // [-INCR] (Incorrect)\82Ì\8fê\8d\87\82Í\82·\82×\82Ä\82Ì\83f\81[\83^
889 // [-ERR (\83e\83L\83X\83g)]\82Ì\8fê\8d\87\82Í\82È\82ñ\82©\83G\83\89\81[
890 // \97á\81F+OK 23094/512K
891 //               -INCR 23094/512K
892 //               -ERR \82»\82ñ\82È\94Â\82È\82¢\82Å\82·
893         Idx := AnsiPos(#10, Content);
894         StatusLine := Copy(Content, 0, Idx);
895
896         Idx := AnsiPos(' ', Content);
897         Status := Copy(StatusLine, 0, Idx - 1);
898         Line := Copy(StatusLine, Idx + 1, Length(StatusLine));
899
900 //      Idx := AnsiPos('/', Line);
901         if Trim(Status) = '-ERR' then
902                 Msg := Trim(Line)
903         else
904                 Size := Copy(Line, 0, Idx - 1);
905
906         if Trim(Status) = '+OK' then
907                 Result.FStatus := gcsOK
908         else if Trim(Status) = '-INCR' then
909                 Result.FStatus := gcsINCR
910         else if Trim(Status) = '-ERR' then begin
911                 Result.FStatus := gcsERR;
912                 Result.FErrText := Msg;
913                 Result.FSize := 0;
914                 Exit;
915         end else begin
916                 {$IFDEF DEBUG}
917                 Writeln(Status);
918                 {$ENDIF}
919                 Result.FStatus := gcsERR;
920                 Result.FErrText := '\83G\83\89\81[\82È\82ñ\82¾\82¯\82Ç\81A\82æ\82­\95ª\82©\82ç\82È\82¢\83G\83\89\81[';
921                 Result.FSize := 0;
922                 Exit;
923         end;
924
925         if GikoSys.IsNumeric(Size) then
926                 Result.FSize := StrToInt(Size)
927         else begin
928                 Result.FSize := 0;
929                 Result.FStatus := gcsERR;
930                 Result.FErrText := '\83X\83e\81[\83^\83X\89ð\90Í\8e¸\94s[' + StatusLine + ']';
931         end;
932 end;
933
934 //\8eè\94²\82«\82È\8f\88\97\9d\82Å1\8ds\96Ú\82ð\8fÁ\82·
935 function TDownloadThread.DeleteStatusLine(Content: string): string;
936 var
937         SList: TStringList;
938 begin
939         SList := TStringList.Create;
940         try
941                 SList.Text := Content;
942                 if SList.Count > 1 then
943                         SList.Delete(0);
944                 Result := SList.Text;
945         finally
946                 SList.Free;
947         end;
948 end;
949
950 procedure TDownloadItem.SaveListFile;
951 var
952         i: Integer;
953         index: Integer;
954         NewItem: TThreadItem;
955         NumCount: Integer;
956         Body: TStringList;
957         Rec: TSubjectRec;
958         st, rt : Cardinal;
959         function MakeThreadCallBack(
960                 inInstance      : DWORD;        // TBoardItem \82Ì\83C\83\93\83X\83^\83\93\83X
961                 inURL                           : PChar;        // \83X\83\8c\83b\83h\82Ì URL
962                 inTitle                 : PChar;        // \83X\83\8c\83^\83C
963                 inCount                 : DWORD         // \83\8c\83X\82Ì\90\94
964         ) : Boolean; stdcall;           // \97ñ\8b\93\82ð\91±\82¯\82é\82È\82ç True
965         var
966                 _ThreadItem     : TThreadItem;  // '_' \82Í\83N\83\89\83X\82Ì\83v\83\8d\83p\83e\83B\82Æ\82©\82Ô\82Á\82Ä\82é\82Ì\82Å
967                 boardItem               : TBoard;
968         begin
969                 Result          := True;
970                 boardItem       := TBoard( inInstance );
971
972                 boardItem.IntData := boardItem.IntData + 1;
973                 if boardItem.IntData < (boardItem.Count shr      2) then
974                         index := boardItem.GetIndexFromURL( string( inURL ) )
975                 else
976                         index := boardItem.GetIndexFromURL( string( inURL ), True );
977                 if index = -1 then begin
978                         //\90V\82µ\82¢\83X\83\8c\83b\83h
979                         _ThreadItem := TThreadItem.Create( boardItem.BoardPlugIn, boardItem, string( inURL ) );
980
981                         _ThreadItem.Title                                       := string( inTitle );
982                         _ThreadItem.AllResCount         := inCount;
983                         _ThreadItem.ParentBoard         := Board;
984                         _ThreadItem.No                                          := boardItem.IntData;
985                         _ThreadItem.RoundDate           := ZERO_DATE;
986                         _ThreadItem.LastModified        := ZERO_DATE;
987                         _ThreadItem.AgeSage                             := gasNew;
988                         boardItem.Add(_ThreadItem);
989                 end else begin
990                         //\8f\87\88Ê\82ª\8fã\82ª\82Á\82Ä\82¢\82ê\82ÎAge\82É\82·\82é
991                         if boardItem.Items[index].No > boardItem.IntData then
992                                 boardItem.Items[index].AgeSage := gasAge
993                         //\8f\87\88Ê\82ª\8fã\82ª\82Á\82Ä\82È\82¢\82¯\82Ç\81A\83\8c\83X\82ª\82Â\82¢\82Ä\82½\82ç\81ASage\82É
994                         else if boardItem.Items[index].AllResCount <> inCount then
995                                 boardItem.Items[index].AgeSage := gasSage
996                         //\8f\87\88Ê\8fã\82ª\82Á\82Ä\82È\82¢\82µ\81A\83\8c\83X\82Ì\91\9d\8c¸\82à\96³\82¯\82ê\82Î\81ANone
997                         else
998                                 boardItem.Items[index].AgeSage := gasNone;
999
1000                         boardItem.Items[index].No                                               := boardItem.IntData;
1001                         boardItem.Items[index].AllResCount      := inCount;
1002                 end;
1003
1004         end;
1005 begin
1006 {$IFDEF DEBUG}
1007         st := GetTickCount;
1008         Writeln('SAVELIST');
1009 {$ENDIF}
1010         //Board.ListData := TList.Create;
1011         Body := TStringList.Create;
1012         try
1013                 //\83_\83E\83\93\83\8d\81[\83h\93ú\8e\9e\90Ý\92è\81i\83\8d\81[\83J\83\8b\93ú\8e\9e\81j
1014                 Board.RoundDate := Now;
1015                 //\83T\81[\83o\8fã\83t\83@\83C\83\8b\82Ì\8dX\90V\8e\9e\8d\8f\90Ý\92è
1016                 Board.LastModified := LastModified;
1017
1018
1019                 //dat\97\8e\82¿\83X\83\8c\82Ì\83\\81[\83g\8f\87\82ð\8c\88\92è\82·\82é\82½\82ß\82É\83\\81[\83g\82·\82é
1020                 if GikoSys.Setting.DatOchiSortIndex >= 0 then begin
1021                         Sort.SortNoFlag := true;
1022                         Sort.SortOrder := GikoSys.Setting.DatOchiSortOrder;
1023                         Sort.SortIndex := GikoSys.Setting.DatOchiSortIndex;
1024                         //Sort.SortNonAcquiredCountFlag := GikoSys.Setting.NonAcquiredCount;
1025                         Board.CustomSort(ThreadItemSortProc);
1026                 end;
1027
1028 {$IFDEF DEBUG}
1029         rt := GetTickCount - st;
1030         Writeln('END Sortd' + IntToStr(rt) + ' ms');
1031 {$ENDIF}
1032
1033                 for i := Board.Count - 1 downto 0 do
1034                         Board.Items[i].AgeSage := gasNull;
1035
1036                 if Board.IsBoardPlugInAvailable then begin
1037                         // \90V\82µ\82¢\83\8a\83X\83g\82ð\8dì\90¬\82·\82é
1038                         // \90V\82µ\82¢\83\8a\83X\83g\82É\8cÃ\82¢\83\8a\83X\83g\82Ì\83\8d\83O\82ª\82 \82é\82È\82ç\82»\82ê\82ð\90V\82µ\82¢\83\8a\83X\83g\82É\92Ç\89Á
1039                         // \8cÃ\82¢\83\8d\83O\82ª\82È\82¯\82ê\82Î\81A\90V\82½\82É\83X\83\8c\83I\83u\83W\83F\83N\83g\82ð\8dì\90¬
1040                         Board.IntData := 0;
1041 {$IFDEF DEBUG}
1042         rt := GetTickCount - st;
1043         Writeln('Start Enum' + IntToStr(rt) + ' ms');
1044 {$ENDIF}
1045
1046                         //\82±\82ê\82ª\92x\82¢\81@\97v\89ü\91P
1047                         Board.BeginUpdate;
1048                         Board.BoardPlugIn.EnumThread( DWORD( Board ), @MakeThreadCallBack );
1049                         Board.EndUpdate;
1050
1051 {$IFDEF DEBUG}
1052         rt := GetTickCount - st;
1053         Writeln('End Enum' + IntToStr(rt) + ' ms');
1054 {$ENDIF}
1055
1056                         //\8cÃ\82¢\83\8a\83X\83g\82É\82µ\82©\82È\82¢\82â\82Â\82ç\82ð\8dí\8f\9c
1057                         for i := Board.Count - 1 downto 0 do begin
1058                                 if( Board.Items[i].AgeSage = gasNull )and not (Board.Items[i].IsLogFile) then
1059                                         Board.Delete(i);
1060                         end;
1061
1062                         // \90V\82µ\82¢\83\8a\83X\83g\82É\96³\82©\82Á\82½\83A\83C\83e\83\80\82ð\90V\82µ\82¢\83\8a\83X\83g\82É\92Ç\89Á
1063                         for i := 0 to Board.Count - 1 do begin
1064                                 if(Board.Items[i].AgeSage = gasNull) and (Board.Items[i].IsLogFile) then begin
1065                                         Board.IntData := Board.IntData + 1;
1066                                         Board.Items[i].No                                               := Board.IntData;
1067                                         Board.Items[i].AllResCount      := Board.Items[i].Count;
1068                                         Board.Items[i].NewResCount      := 0;
1069                                         Board.Items[i].AgeSage                  := gasNone;
1070                                 end;
1071                         end;
1072
1073                 end else begin
1074                         //\90V\82µ\82¢\83\8a\83X\83g\82ð\8dì\90¬\82·\82é
1075                         //\90V\82µ\82¢\83\8a\83X\83g\82É\8cÃ\82¢\83\8a\83X\83g\82Ì\83\8d\83O\82ª\82 \82é\82È\82ç\82»\82ê\82ð\90V\82µ\82¢\83\8a\83X\83g\82É\92Ç\89Á
1076                         //\8cÃ\82¢\83\8d\83O\82ª\82È\82¯\82ê\82Î\81A\90V\82½\82É\83X\83\8c\83I\83u\83W\83F\83N\83g\82ð\8dì\90¬
1077                         Body.Text := Content;
1078                         NumCount := 0;
1079                         for i := 0 to Body.Count - 1 do begin
1080                                 Rec := GikoSys.DivideSubject(Body[i]);
1081                                 Rec.FFileName := Trim(Rec.FFileName);
1082                                 if (Rec.FTitle = '') and (Rec.FCount = 0) then Continue;
1083                                 Inc(NumCount);
1084                                 index := Board.GetIndexFromFileName(Rec.FFileName);
1085                                 if index = -1 then begin
1086                                         //\90V\82µ\82¢\83X\83\8c\83b\83h
1087                                         NewItem := TThreadItem.Create(
1088                                                                 nil,
1089                                 Board,
1090                                 GikoSys.Get2chBoard2ThreadURL( Board, ChangeFileExt( Rec.FFileName, '' ) ) );
1091                                         NewItem.Title := Rec.FTitle;
1092                                         NewItem.AllResCount := Rec.FCount;
1093                                         NewItem.ParentBoard := Board;
1094                                         NewItem.No := NumCount;
1095                                         NewItem.RoundDate := ZERO_DATE;
1096                                         NewItem.LastModified := ZERO_DATE;
1097                                         NewItem.AgeSage := gasNew;
1098                                         Board.Add(NewItem);
1099                                 end else begin
1100                                         if Board.Items[index].No > NumCount then
1101                                                 Board.Items[index].AgeSage := gasAge
1102                                         else if Board.Items[index].AllResCount < Rec.FCount then
1103                                                 Board.Items[index].AgeSage := gasSage
1104                                         else
1105                                                 Board.Items[index].AgeSage := gasNone;
1106
1107                                         Board.Items[index].No := NumCount;
1108                                         Board.Items[index].AllResCount := Rec.FCount;
1109                                 end;
1110                         end;
1111                         //\8cÃ\82¢\83\8a\83X\83g\82Ì\8dí\8f\9c
1112                         for i := Board.Count - 1 downto 0 do begin
1113                                 if( Board.Items[i].AgeSage = gasNull )and not (Board.Items[i].IsLogFile) then
1114                                         Board.Delete(i);
1115                         end;
1116
1117                         //\90V\82µ\82¢\83\8a\83X\83g\82É\96³\82©\82Á\82½\83A\83C\83e\83\80\82Ì\8dX\90V
1118                         for i := 0 to Board.Count - 1 do begin
1119                                 if( Board.Items[i].AgeSage = gasNull )and (Board.Items[i].IsLogFile) then begin
1120                                         inc(NumCount);
1121                                         Board.Items[i].No := NumCount;
1122                                         Board.Items[i].AllResCount := Board.Items[i].Count;
1123                                         Board.Items[i].NewResCount := 0;
1124                                         Board.Items[i].AgeSage := gasNone;
1125                                 end;
1126                         end;
1127                         //\83\8a\83X\83g(subject.txt)\82ð\95Û\91
1128                         GikoSys.ForceDirectoriesEx(ExtractFilePath(Board.GetSubjectFileName));
1129                         Body.SaveToFile(Board.GetSubjectFileName);
1130                 end;
1131         finally
1132                 Body.Free;
1133         end;
1134
1135
1136 end;
1137
1138 {procedure TDownloadItem.SaveListFile;
1139 var
1140         i: Integer;
1141         index: Integer;
1142         NewItem: TThreadItem;
1143         NewList: TList;
1144 //      SaveCount: Integer;
1145         NumCount: Integer;
1146         Body: TStringList;
1147         Rec: TSubjectRec;
1148 begin
1149         NewList := TList.Create;
1150         Body := TStringList.Create;
1151         try
1152                 //\8f\84\89ñ\93ú\8e\9e\90Ý\92è
1153                 Board.RoundDate := Now;
1154                 //\83T\81[\83o\8fã\83t\83@\83C\83\8b\82Ì\8dX\90V\8e\9e\8d\8f\90Ý\92è
1155                 Board.LastModified := LastModified;
1156
1157                 //\83\8a\83X\83g\95Û\91\8c\8f\90\94\8eæ\93¾
1158                 //SaveCount := MaxInt;
1159
1160                 //\8cÃ\82¢\83\8a\83X\83g\82©\82ç\83\8d\83O\96³\82µ\83A\83C\83e\83\80\82ð\8dí\8f\9c
1161                 for i := Board.Count - 1 downto 0 do
1162                         if not Board.Items[i].IsLogFile then
1163                                 Board.Delete(i);
1164
1165                 //\90V\82µ\82¢\83\8a\83X\83g\82ð\8dì\90¬\82·\82é
1166                 //\90V\82µ\82¢\83\8a\83X\83g\82É\8cÃ\82¢\83\8a\83X\83g\82Ì\83\8d\83O\82ª\82 \82é\82È\82ç\82»\82ê\82ð\90V\82µ\82¢\83\8a\83X\83g\82É\92Ç\89Á
1167                 //\8cÃ\82¢\83\8d\83O\82ª\82È\82¯\82ê\82Î\81A\90V\82½\82É\83X\83\8c\83I\83u\83W\83F\83N\83g\82ð\8dì\90¬
1168                 Body.Text := Content;
1169 //              Loop := Min(Body.Count, SaveCount);
1170                 NumCount := 0;
1171 //              for i := 0 to Loop - 1 do begin
1172                 for i := 0 to Body.Count - 1 do begin
1173                         if i = 0 then Continue; //\82P\8ds\96Ú\82Í\83X\83e\81[\83^\83X\8ds\82Ì\82½\82ß\8f\88\97\9d\82È\82µ
1174
1175                         Rec := GikoSys.DivideSubject(Body[i]);
1176                         if (Rec.FTitle = '') and (Rec.FCount = 0) then Continue;
1177                         Inc(NumCount);
1178                         index := Board.GetIndex(Rec.FFileName);
1179                         if index = -1 then begin
1180                                 NewItem := TThreadItem.Create;
1181                                 NewItem.FileName := Rec.FFileName;
1182                                 NewItem.Title := Rec.FTitle;
1183                                 NewItem.Count := Rec.FCount;
1184                                 NewItem.ParentBoard := Board;
1185                                 NewItem.No := NumCount;
1186                                 NewItem.RoundDate := ZERO_DATE;
1187                                 NewItem.LastModified := ZERO_DATE;
1188                                 NewList.Add(NewItem);
1189                         end else begin
1190                                 //Board.Items[index].Count := Count;
1191                                 Board.Items[index].No := NumCount;
1192                                 NewList.Add(Board.Items[index]);
1193                                 Board.DeleteList(index);
1194                         end;
1195                 end;
1196
1197                 //\90V\82µ\82¢\83\8a\83X\83g\82É\96³\82©\82Á\82½\8cÃ\82¢\83\8d\83O\97L\82è\83A\83C\83e\83\80\82ð\90V\82µ\82¢\83\8a\83X\83g\82É\92Ç\89Á
1198                 for i := 0 to Board.Count - 1 do begin
1199                         inc(NumCount);
1200                         Board.Items[i].No := NumCount;
1201                         NewList.Add(Board.Items[i]);
1202                 end;
1203
1204                 //\8cÃ\82¢\83\8a\83X\83g\82ð\8fÁ\82·\81i\83\8a\83X\83g\82Ì\82Ý\81B\83X\83\8c\83I\83u\83W\83F\83N\83g\8e©\91Ì\82Í\8fÁ\82³\82È\82¢\81j
1205                 for i := Board.Count - 1 downto 0 do
1206                         Board.DeleteList(i);
1207
1208                 //\90V\82µ\82¢\83\8a\83X\83g\82ð\83{\81[\83h\83I\83u\83W\83F\83N\83g\82É\92Ç\89Á
1209                 for i := 0 to NewList.Count - 1 do
1210                         Board.Add(TThreadItem(NewList[i]));
1211
1212                 //\83\8a\83X\83g(subject.txt)\82ð\95Û\91
1213 //              GikoSys.ForceDirectoriesEx(GikoSys.GetLogDir + Board.BBSID);
1214 //              Body.SaveToFile(GikoSys.GetSubjectFileName(Board.BBSID));
1215                 GikoSys.ForceDirectoriesEx(ExtractFilePath(Board.GetSubjectFileName));
1216                 Body.SaveToFile(Board.GetSubjectFileName);
1217         finally
1218                 Body.Free;
1219                 NewList.Free;
1220         end;
1221 end;
1222 }
1223 procedure TDownloadItem.SaveItemFile;
1224 var
1225         Body, oldBody: TStringList;
1226         Cnt: Integer;
1227         OldCnt: Integer;
1228         FileName: string;
1229         ini: TMemIniFile;
1230         Res: TResRec;
1231         NewRes: Integer;
1232         finish : Boolean;
1233         loopCnt : Integer;
1234 //      KokoTxt : string;
1235 //      KokoIdx : Integer;
1236 //      NewTxt  : string;
1237 //      NewIdx  : Integer;
1238 //      LastTxt : string;
1239         LastIdx : Integer;
1240 begin
1241         FileName := ThreadItem.GetThreadFileName;
1242
1243         //if not ThreadItem.IsBoardPlugInAvailable then begin
1244     if not ThreadItem.ParentBoard.IsBoardPlugInAvailable then begin
1245                 if Trim(Content) = '' then
1246                         Exit;
1247
1248                 GikoSys.ForceDirectoriesEx(ExtractFilePath(FileName));
1249
1250                 //      Cnt := 0;
1251                 Body := TStringList.Create;
1252                 NewRes := 0;
1253                 OldCnt := 0;
1254                 try
1255                 //              if FileExists(FileName) and (ResponseCode = 206) then begin
1256                         if FileExists(FileName) and (State = gdsDiffComplete) then begin
1257                                 loopCnt := 10;
1258                                 repeat
1259                                         finish := true;
1260                                         try
1261                                                 Body.LoadFromFile(FileName);
1262                                                 OldCnt := Body.Count;
1263                                                 Body.Text := Body.Text + Content;
1264                                                 Body.SaveToFile(FileName);
1265                                                 NewRes := Body.Count - OldCnt;
1266                                         except
1267                                                 on E:EFOpenError do begin
1268                                                         sleep(10);
1269                                                         Dec(loopCnt);
1270                                                         if loopCnt > 0 then
1271                                                                 finish := false;
1272                                                 end;
1273                                         end;
1274                                 until finish;
1275                                 //Cnt := Body.Count;
1276                         end else begin
1277                                 if IsAbone then begin
1278                                         // \82 \82Ú\81[\82ñ\82ð\8c\9f\8fo\82µ\82½\82Ì\82Å\82±\82±\82Ü\82Å\93Ç\82ñ\82¾\82Æ\90V\92\85\83\8c\83X\94Ô\82Ì\82Â\82¯\82È\82¨\82µ
1279                                         oldBody := TStringList.Create;
1280                                         try
1281                                                 loopCnt := 10;
1282                                                 repeat
1283                                                         finish := true;
1284                                                         try
1285                                                                 oldBody.LoadFromFile(FileName);
1286                                                         except
1287                                                                 on E:EFOpenError do begin
1288                                                                         sleep(10);
1289                                                                         Dec(loopCnt);
1290                                                                         if loopCnt > 0 then
1291                                                                                 finish := false
1292                                                                         else
1293                                                                                 finish := true;
1294                                                                 end;
1295                                                         end;
1296                                                 until finish;
1297
1298                                                 Body.Text := Content;
1299                                                 if (ThreadItem.Kokomade > 0) and (ThreadItem.Kokomade <= oldBody.Count) then begin
1300                                                         ThreadItem.Kokomade := Body.IndexOf(oldBody.Strings[ ThreadItem.Kokomade - 1 ]);
1301                                                         if ThreadItem.Kokomade <> -1 then ThreadItem.Kokomade := ThreadItem.Kokomade + 1;
1302                                                 end;
1303
1304                                                 LastIdx := oldBody.Count;
1305                                                 repeat
1306                                                         Dec(LastIdx);
1307                                                         OldCnt := Body.IndexOf(oldBody.Strings[ LastIdx ]) + 1;
1308                                                 until ( OldCnt <> 0 ) or (LastIdx = 0);
1309
1310                                                 if OldCnt >= Body.Count then OldCnt := Body.Count - 1;
1311                                                 NewRes := Body.Count - OldCnt;
1312
1313                                                 // \82±\82±\82Ü\82Å\93Ç\82ñ\82¾\82ª\90V\92\85\83\8c\83X\94Ô\82ð\92´\82³\82È\82¢\82æ\82¤\82É(\88Ù\8fí\8fI\97¹\8e\9e\82Ì\83\8a\83J\83o\83\8a)
1314                                                 if ThreadItem.Kokomade > OldCnt then begin
1315                                                         if OldCnt > 0 then
1316                                                                 ThreadItem.Kokomade := OldCnt
1317                                                         else
1318                                                                 ThreadItem.Kokomade := 1;
1319                                                 end;
1320
1321                                         finally
1322                                                 oldBody.Free;
1323                                         end;
1324
1325                                 end else begin
1326                                         Body.Text := Content;
1327                                         //ThreadItem.Count := 0;
1328                                         OldCnt := 0;
1329                                         NewRes := Body.Count;
1330                                         //Cnt := Body.Count;
1331                                 end;
1332         //                      if Body.Count > 0 then
1333         //                              Body.Delete(0);
1334                                 Body.SaveToFile(FileName);
1335
1336                                 if ThreadItem.Title = '' then begin
1337                                         Res := GikoSys.DivideStrLine(Body[0]);
1338                                         ThreadItem.Title := Res.FTitle;
1339                                 end;
1340                                 ThreadItem.Size := 0;
1341                         end;
1342                         Cnt := Body.Count;
1343                 finally
1344                         Body.Free;
1345                 end;
1346
1347                 ThreadItem.Size := ThreadItem.Size + ContentLength;
1348                 ThreadItem.LastModified := LastModified;
1349                 ThreadItem.Count := Cnt;
1350                 //ThreadItem.AllResCount := Cnt;
1351                 ThreadItem.NewResCount := NewRes;
1352                 ThreadItem.NewReceive := OldCnt + 1;
1353         end;
1354         ThreadItem.AllResCount := ThreadItem.Count;
1355         ThreadItem.IsLogFile := True;
1356         ThreadItem.RoundDate := Now;
1357         if not ThreadItem.UnRead then begin
1358                 ThreadItem.UnRead := True;
1359                 ThreadItem.ParentBoard.UnRead := ThreadItem.ParentBoard.UnRead + 1;
1360         end;
1361 //      if ThreadItem.RoundNo = 6 then
1362 //              ThreadItem.RoundNo := 0;
1363
1364         //\88Ù\8fí\8fI\97¹\8e\9e\82Í\83C\83\93\83f\83b\83N\83X\82ª\8dX\90V\82³\82ê\82È\82¢\82½\82ß\81A\83e\83\93\83|\83\89\83\8a\82ð\8dì\90¬\82·\82é\81B
1365         //\90³\8fí\8fI\97¹\8e\9e\82É\82Í\8dí\8f\9c
1366         //\88Ù\8fí\8fI\97¹\8e\9e\82Í\81A\8e\9f\89ñ\8bN\93®\8e\9e\82É\83e\83\93\83|\83\89\83\8a\82ð\8c©\82Ä\8dX\90V
1367         ini := TMemIniFile.Create(ChangeFileExt(FileName, '.tmp'));
1368         try
1369                 ini.WriteDateTime('Setting', 'RoundDate', ThreadItem.RoundDate);
1370                 ini.WriteDateTime('Setting', 'LastModified', ThreadItem.LastModified);
1371                 ini.WriteInteger('Setting', 'Size', ThreadItem.Size);
1372                 ini.WriteInteger('Setting', 'Count', ThreadItem.Count);
1373                 ini.WriteInteger('Setting', 'AllResCount', ThreadItem.AllResCount);
1374                 ini.WriteInteger('Setting', 'NewResCount', ThreadItem.NewResCount);
1375                 ini.WriteInteger('Setting', 'NewReceive', ThreadItem.NewReceive);
1376 //              ini.WriteInteger('Setting', 'RoundNo', ThreadItem.RoundNo);
1377                 ini.WriteBool('Setting', 'Round', ThreadItem.Round);
1378                 ini.WriteBool('Setting', 'UnRead', ThreadItem.UnRead);
1379                 ini.UpdateFile;
1380         finally
1381                 ini.Free;
1382         end;
1383 end;
1384
1385 end.