/// <summary> /// 別スレッドとして受信処理を行うメソッドです。 /// </summary> private void OpenInternal() { // 完了状態を表す CompleteStatus status = CompleteStatus.Success; List <ThreadHeader> items = null; try { OnLoading(new EventArgs()); if (OpenReader(boardInfo)) { items = Reading(); if (canceled) { return; } headerList.AddRange(items); Invoke(new WriteListMethodInvoker(WriteInternal), new object[] { items }); } } catch (Exception ex) { status = CompleteStatus.Error; OnStatusTextChanged(ex.Message); TwinDll.Output(ex); } finally { if (canceled) { status = CompleteStatus.Error; } if (baseReader != null) { baseReader.Close(); } canceled = false; if (thread != null) { lock (thread) thread = null; } OnComplete(new CompleteEventArgs(status)); if (status == CompleteStatus.Success) { OnStatusTextChanged( String.Format("{0}板の読み込みを完了 (総数: {1})", boardInfo.Name, headerList.Count)); } } }
/// <summary> /// 指定した板の既得インデックスを読み込む /// </summary> /// <param name="cache"></param> /// <param name="board"></param> /// <returns></returns> public static List <ThreadHeader> Read(Cache cache, BoardInfo board) { if (cache == null) { throw new ArgumentNullException("cache"); } if (board == null) { throw new ArgumentNullException("board"); } List <ThreadHeader> items = new List <ThreadHeader>(); string indicesPath = GetIndicesPath(cache, board); if (File.Exists(indicesPath)) { XmlDocument document = new XmlDocument(); lock (typeof(GotThreadListIndexer)) { document.Load(indicesPath); } XmlElement root = document.DocumentElement; XmlNodeList children = root.ChildNodes; foreach (XmlNode node in children) { try { ThreadHeader header = TypeCreator.CreateThreadHeader(board.Bbs); header.BoardInfo = board; header.Key = node.Attributes.GetNamedItem("key").Value; header.Subject = node.SelectSingleNode("subject").InnerText; int resCount; if (Int32.TryParse(node.SelectSingleNode("resCount").InnerText, out resCount)) { header.ResCount = resCount; } items.Add(header); } catch (Exception ex) { TwinDll.Output(ex); } } } return(items); }
/// <summary> /// 指定した板のログと書込履歴を削除 /// </summary> /// <param name="board"></param> /// <returns></returns> public virtual void Remove(BoardInfo board) { if (board != null) { try { string folder = GetFolderPath(board); Directory.Delete(folder, true); } catch (Exception ex) { TwinDll.Output(ex); } } }
/// <summary> /// oldBoardからnewBoardにログを移動 /// </summary> /// <param name="oldItems"></param> /// <param name="newBoard"></param> private void CopyDatFiles(BoardInfo oldItems, BoardInfo newBoard) { string fromFolder = cache.GetFolderPath(oldItems); string toFolder = cache.GetFolderPath(newBoard, true); string[] fileNames = Directory.GetFiles(fromFolder, "*.dat*"); // .dat .dat.gz を検索 foreach (string fromPath in fileNames) { try { string fromName = Path.GetFileName(fromPath); string destPath = Path.Combine(toFolder, fromName); File.Copy(fromPath, destPath, true); File.Delete(fromPath); int token = fromName.IndexOf('.'); if (token != -1) { string key = fromName.Substring(0, token); string fromIndexFile = Path.Combine(fromFolder, key + ".idx"); string toIndexFile = Path.Combine(toFolder, key + ".idx"); ThreadHeader h = ThreadIndexer.Read(fromIndexFile); if (h != null) { h.BoardInfo.Server = newBoard.Server; ThreadIndexer.Write(toIndexFile, h); File.Delete(fromIndexFile); } } } catch (IOException ex) { TwinDll.Output(ex); } } }
/// <summary> /// インデックスを読み込む /// </summary> /// <param name="filePath">インデックスファイルへのファイルパス</param> /// <returns>読み込みに成功すればThreadHeaderのインスタンス、失敗すればnull</returns> public static ThreadHeader Read(string filePath) { if (filePath == null) { throw new ArgumentNullException("filePath"); } ThreadHeader result = null; lock (typeof(ThreadIndexer)) { // インデックスファイルへのパス if (File.Exists(filePath)) { try { CSPrivateProfile profile = new CSPrivateProfile(); profile.Read(filePath); // 重要なセクションがなければエラー if (!profile.Sections.ContainsSection("Board") || !profile.Sections.ContainsSection("Thread")) { return(null); } BbsType bbs = (BbsType)Enum.Parse(typeof(BbsType), profile.GetString("Board", "BBS", "X2ch")); // 板情報 result = TypeCreator.CreateThreadHeader(bbs); result.BoardInfo.Server = profile.GetString("Board", "Server", "Error"); result.BoardInfo.Path = profile.GetString("Board", "Path", "Error"); result.BoardInfo.Name = profile.GetString("Board", "Name", "Error"); result.BoardInfo.Bbs = bbs; // スレッド情報 result.ETag = profile.GetString("Thread", "ETag", String.Empty); result.LastWritten = profile.GetDateTime("Thread", "LastWritten"); result.LastModified = profile.GetDateTime("Thread", "LastModified"); result.Subject = profile.GetString("Thread", "Subject", "Error"); result.ResCount = profile.GetInt("Thread", "ResCount", 0); result.GotResCount = profile.GetInt("Thread", "GotResCount", 0); result.GotByteCount = profile.GetInt("Thread", "GotByteCount", 0); result.NewResCount = profile.GetInt("Thread", "NewResCount", 0); result.Key = profile.GetString("Thread", "Key", "Error"); // そのほかの情報 result.Position = profile.GetFloat("Option", "Position", 0); result.Pastlog = profile.GetBool("Option", "Pastlog", false); result.UseGzip = profile.GetBool("Option", "UseGzip", false); result.Shiori = profile.GetInt("Option", "Shiori", 0); result.RefCount = profile.GetInt("Option", "RefCount", 0); result.Sirusi.FromArrayString(profile.GetString("Option", "Sirusi", "")); } catch (Exception ex) { TwinDll.Output(ex); } } } return(result); }
/// <summary> /// オンラインで板一覧を更新 ([BBS MENU for 2ch]に対応) /// </summary> /// <param name="url">更新先URL</param> /// <param name="callback">板が移転していた場合に呼ばれるコールバック</param> public void OnlineUpdate(string url, BoardUpdateEventHandler callback) { HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); req.Headers.Add("Pragma", "no-cache"); req.Headers.Add("Cache-Control", "no-cache"); HttpWebResponse res = (HttpWebResponse)req.GetResponse(); try { IBoardTable newTable = new KatjuBoardTable(); string htmlData; using (StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.GetEncoding("Shift_Jis"))) htmlData = sr.ReadToEnd(); res.Close(); res = null; // 2012/12/05 Mizutama実装 // 板情報を抽出 // <BR><BR><B>カテゴリ名</B><BR> // <A HREF=http://[サーバー]/[板名]/>名前</A> MatchCollection cats = Regex.Matches ( htmlData, @"<BR><BR><B>(?<cat>.+?)</B><BR>(?<brds>.+?)(?=\<BR\>\<BR\>\<B\>)", RegexOptions.Singleline | RegexOptions.IgnoreCase ); foreach (Match m in cats) { Category category = new Category(m.Groups["cat"].Value); MatchCollection brds = Regex.Matches ( m.Groups["brds"].Value, @"<A HREF=(?<url>[^\s>]+).*?>(?<subj>.+?)</A>", RegexOptions.Singleline | RegexOptions.IgnoreCase ); foreach (Match matchBrd in brds) { // ボード情報を作成 BoardInfo newBoard = URLParser.ParseBoard(matchBrd.Groups["url"].Value); if (newBoard != null) { newBoard.Name = matchBrd.Groups["subj"].Value; category.Children.Add(newBoard); if (callback != null) { // 新板&移転チェック BoardInfo old = FromName(newBoard.Name, newBoard.DomainPath); BoardUpdateEventArgs args = null; // 見つからなければ新板と判断 if (old == null) { args = new BoardUpdateEventArgs(BoardUpdateEvent.New, null, newBoard); } // 見つかったが板のURLが違う場合は移転と判断 else if (old.Server != newBoard.Server) { args = new BoardUpdateEventArgs(BoardUpdateEvent.Change, old, newBoard); } if (args != null) { callback(this, args); } } } } if (category.Children.Count > 0) { newTable.Items.Add(category); } } if (newTable.Items.Count > 0) { // 新しい板一覧を設定 Items.Clear(); Items.AddRange(newTable.Items); } else { throw new ApplicationException("板一覧の更新に失敗しました"); } } catch (ThreadAbortException) { if (callback != null) { callback(this, new BoardUpdateEventArgs(BoardUpdateEvent.Cancelled, null, null)); } } catch (Exception ex) { TwinDll.ShowOutput(ex); } finally { if (res != null) { res.Close(); } } }
/// <summary> /// textを解析 /// </summary> /// <param name="data"></param> /// <returns></returns> protected virtual string Parse(string text) { if (text == null) { throw new ArgumentNullException("text"); } try { // 2ch_Xを検索する正規表現 Regex regex2chx = new Regex(@"<!--\s*?(2ch_X:\w+)\s*?-->", RegexOptions.IgnoreCase | RegexOptions.Singleline); // タイトルを検索する正規表現 Regex regext = new Regex(@"<title>(?<t>.+?)</title>", RegexOptions.IgnoreCase | RegexOptions.Singleline); // 本文を検索する正規表現 Regex regexb = new Regex(@"(<body.*?>(?<b>.+)</body>)|(</head>(?<b>.+)</body>)", RegexOptions.IgnoreCase | RegexOptions.Singleline); // タイトルを取得 Match match0 = regext.Match(text); if (match0.Success) { title = match0.Groups["t"].Value; response = TitleToStatus(title); } // 2ch_Xを取得 Match match1 = regex2chx.Match(text); if (match1.Success) { response = x2chXToStatus(match1.Value); } // 本文を取得(タグを取り除く) Match match2 = regexb.Match(text); if (match2.Success) { string result = match2.Groups["b"].Value; if (result.IndexOf("書き込み確認") != -1) { response = PostResponse.Cookie; } else if (result.IndexOf("クッキーをオンにしてちょ") != -1) { response = PostResponse.Cookie; } else if (result.IndexOf("ERROR - 593") != -1) { response = PostResponse.Samba; Match m = Regex.Match(result, "593 (?<cnt>\\d+) sec"); if (m.Success) { Int32.TryParse(m.Groups["cnt"].Value, out sambaCount); } } result = Regex.Replace(result, "<br>", "\r\n", RegexOptions.IgnoreCase); result = Regex.Replace(result, "<hr>", "\r\n", RegexOptions.IgnoreCase); result = Regex.Replace(result, "</?[^>]+>", ""); result = Regex.Replace(result, "\t", ""); result = Regex.Replace(result, "(\r\n|\r|\n){2,}", "\r\n", RegexOptions.Singleline); plainText = result; if (response == PostResponse.Cookie) { MatchCollection matches = Regex.Matches(text, "<input type=hidden name=\"?(\\w+?)\"? value=\"?(\\w+?)\"?>", RegexOptions.IgnoreCase); foreach (Match m in matches) { string name = m.Groups[1].Value; string value = m.Groups[2].Value; hiddenParamsDic.Add(name, value); #if DEBUG Console.WriteLine("{0}={1}", name, value); #endif } } } if (plainText == String.Empty) { plainText = htmlText; } } catch (Exception ex) { TwinDll.Output(ex.ToString()); } return(plainText); }
private void OpenInternal() { CompleteStatus status = CompleteStatus.Success; try { try { isWaiting = true; OnLoading(new EventArgs()); } finally { isWaiting = false; } if (canceled) { return; } // 開く処理を行う if (modeOpen) { Invoke(new MethodInvoker(Opening)); } // キャッシュを読み込み表示 ReadCache(resCollection); if (canceled) { return; } Invoke(new MethodInvoker(WriteBegin)); if (canceled) { return; } if (modeOpen) { Invoke(new WriteResMethodInvoker(Write), new object[] { resCollection }); // スクロール位置を復元 (値が0の場合は復元しない) if (modeOpen && headerInfo.Position != 0.0f) { Invoke(new PositionMethodInvoker(SetScrollPosition), new object[] { headerInfo.Position }); } } Retry: try { // サーバーに接続 if (OpenReader()) { if (canceled) { return; } Reading(); // あぼーんを検知した場合 if (aboneDetected) { } } else { headerInfo.NewResCount = 0; ThreadIndexer.Write(Cache, headerInfo); } } finally { if (reader != null) { reader.Close(); } } // 再試行が要求された場合、最初から if (retried) { goto Retry; } if (canceled) { return; } Invoke(new MethodInvoker(WriteEnd)); } catch (Exception ex) { status = CompleteStatus.Error; // isOpen = false; #10/15 TwinDll.Output(ex); OnStatusTextChanged(ex.Message); } finally { // 中止された場合はETagをリセット if (canceled) { headerInfo.ETag = String.Empty; } indicesValues.Clear(); canceled = false; lock (syncObject) thread = null; if (status == CompleteStatus.Success) { OnStatusTextChanged( String.Format("{0}の読み込みを完了 (新着: {1}件)", headerInfo.Subject, headerInfo.NewResCount)); } /** 9/26 追加 **/ else if (reader is X2chAuthenticateThreadReader) { X2chRokkaResponseState rokkaState = ((X2chAuthenticateThreadReader)reader).RokkaResponseState; if (rokkaState != X2chRokkaResponseState.Success) { TwinDll.Output("RokkaResponseState: {0}, URL: {1}, ", rokkaState, headerInfo.Url); } OnStatusTextChanged(String.Format("RokkaResponseState: {0}", rokkaState)); } /****/ IsReading = false; lastCompletedDateTime = DateTime.Now; OnComplete(new CompleteEventArgs(status)); } }
/// <summary> /// データを読み込む&書き込む /// </summary> private void Reading() { ResSetCollection items = new ResSetCollection(), buffer = new ResSetCollection(); int read = -1, byteParsed, totalByteCount = 0; while (read != 0) { if (canceled) { return; } read = reader.Read(buffer, out byteParsed); // あぼーんを検知した場合、処理を中止。 if (read == -1) { aboneDetected = true; return; } totalByteCount += byteParsed; items.AddRange(buffer); // 逐次受信の場合はビューアに書き込む if (!isPackageReception) { if (canceled) { return; } Invoke(new WriteResMethodInvoker(WriteInternal), new object[] { buffer }); } buffer.Clear(); OnReceive(new ReceiveEventArgs( reader.Length, reader.Position, read)); OnStatusTextChanged( String.Format("{0} 受信中 ({1}/{2})", headerInfo.Subject, reader.Position, reader.Length)); } // 一括受信の場合はここで一気にフラッシュ if (isPackageReception) { if (canceled) { return; } Invoke(new WriteResMethodInvoker(WriteInternal), new object[] { items }); } try { // スレッドのインデックス情報を保存 storage = new LocalThreadStorage(Cache, headerInfo, StorageMode.Write); storage.BufferSize = bufferSize; storage.Write(items); headerInfo.GotByteCount += totalByteCount; headerInfo.GotResCount += items.Count; headerInfo.NewResCount = items.Count; ThreadIndexer.Write(Cache, headerInfo); } catch (Exception ex) { TwinDll.Output(ex); } finally { storage.Close(); } SaveThreadListIndices(); }