3 import java.io.BufferedInputStream;
\r
4 import java.io.BufferedOutputStream;
\r
5 import java.io.BufferedReader;
\r
6 import java.io.BufferedWriter;
\r
8 import java.io.FileOutputStream;
\r
9 import java.io.FileWriter;
\r
10 import java.io.IOException;
\r
11 import java.io.InputStreamReader;
\r
12 import java.io.OutputStream;
\r
13 import java.net.HttpURLConnection;
\r
14 import java.net.InetSocketAddress;
\r
15 import java.net.MalformedURLException;
\r
16 import java.net.Proxy;
\r
17 import java.net.URL;
\r
18 import java.util.ArrayList;
\r
19 import java.util.Calendar;
\r
20 import java.util.Collections;
\r
21 import java.util.Date;
\r
22 import java.util.GregorianCalendar;
\r
23 import java.util.HashMap;
\r
24 import java.util.List;
\r
25 import java.util.regex.Matcher;
\r
26 import java.util.regex.Pattern;
\r
28 import tainavi.TVProgram.ProgFlags;
\r
29 import tainavi.TVProgram.ProgGenre;
\r
30 import tainavi.TVProgram.ProgOption;
\r
31 import tainavi.TVProgram.ProgScrumble;
\r
32 import tainavi.TVProgram.ProgSubgenre;
\r
36 * {@link TVProgram}インタフェース をインプルメントしたWeb番組表プラグインのクラスで利用できる、共有部品の集合です。
\r
38 public class TVProgramUtils implements Cloneable {
\r
40 /*******************************************************************************
\r
42 ******************************************************************************/
\r
45 public TVProgramUtils clone() {
\r
47 TVProgramUtils p = (TVProgramUtils) super.clone();
\r
49 // フィールドコピーしてもらいたくないもの
\r
52 // static化したのでコピー抑制を必要としなくなったものたち
\r
53 //p.setProgressArea(null);
\r
54 //p.setChConv(null);
\r
56 CommonUtils.FieldCopy(p, this); // ディープコピーするよ
\r
58 p.pcenter = new ArrayList<ProgList>();
\r
62 p.aclist = new ArrayList<AreaCode>();
\r
63 for ( AreaCode ac : aclist ) {
\r
64 p.aclist.add(ac.clone());
\r
68 p.crlist = new ArrayList<Center>();
\r
69 for ( Center cr : crlist ) {
\r
70 p.crlist.add(cr.clone());
\r
74 p.setSortedCRlist();
\r
78 } catch (CloneNotSupportedException e) {
\r
79 throw new InternalError(e.toString());
\r
84 /*******************************************************************************
\r
86 ******************************************************************************/
\r
91 public static boolean setProxy(String host, String port) {
\r
92 Proxy newproxy = null;
\r
93 if ( host != null ) {
\r
95 newproxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, Integer.valueOf(port)));
\r
97 catch (Exception e) {
\r
98 e.printStackTrace();
\r
106 private Proxy getProxy() { return proxy; }
\r
108 private static Proxy proxy = null;
\r
111 * ChannelConvert.dat
\r
113 public static void setChConv(ChannelConvert cc) { chconv = cc; }
\r
115 private ChannelConvert getChConv() { return chconv; }
\r
117 private static ChannelConvert chconv = null;
\r
122 public static void setProgressArea(StatusWindow o) { stw = o; }
\r
124 protected void reportProgress(String msg) {
\r
128 System.out.println(msg);
\r
131 private static StatusWindow stw = null;
\r
134 /*******************************************************************************
\r
136 ******************************************************************************/
\r
138 public boolean isAreaSelectSupported() { return true; } // デフォルトはエリアを選べる
\r
141 /*******************************************************************************
\r
143 ******************************************************************************/
\r
146 private static final int conntout = 5;
\r
149 private static final int readtout = 30;
\r
151 // 番組表が複数ページに跨っている場合、何ページまで追いかけるか
\r
152 protected static final int REFPAGESMAX = 10;
\r
154 // 番組詳細に情報をconcatする時のセパレータ
\r
155 protected final String DETAIL_SEP = "\n\n";
\r
158 private static final String MSGID = "[番組表共通] ";
\r
159 private static final String DBGID = "[DEBUG]"+MSGID;
\r
160 private static final String ERRID = "[ERROR]"+MSGID;
\r
162 /*******************************************************************************
\r
164 ******************************************************************************/
\r
167 public void setDebug(boolean b) { debug = b; }
\r
168 protected boolean getDebug() { return debug; } // 参照はサブクラスのみに許可
\r
169 private boolean debug = false;
\r
172 protected void setRetryCount(int n) { retrycount = n; } // サブクラスのみに許可(特殊)
\r
173 private int retrycount = 1;
\r
176 public String getUserAgent() { return userAgent; }
\r
177 public void setUserAgent(String s) { userAgent = s; }
\r
178 private String userAgent = "";
\r
181 public String getProgDir() { return progDir; }
\r
182 public void setProgDir(String s) { progDir = s; }
\r
183 private String progDir = "progcache";
\r
185 // 番組表キャッシュの有効時間(H)
\r
186 public int getCacheExpired() { return cacheExpired; }
\r
187 public void setCacheExpired(int h) { cacheExpired = h; }
\r
188 private int cacheExpired = 6;
\r
191 public boolean getUseDetailCache() { return useDetailCache; }
\r
192 public void setUseDetailCache(boolean b) { useDetailCache = b; }
\r
193 private boolean useDetailCache = true;
\r
195 // 29時跨ぎで2つに分かれた番組情報をくっつけるかどうか
\r
196 public boolean getContinueTomorrow() { return continueTomorrow; }
\r
197 public void setContinueTomorrow(boolean b) { continueTomorrow = b; }
\r
198 private boolean continueTomorrow = false;
\r
201 public void setSplitEpno(boolean b) { splitEpno = b; }
\r
202 public boolean isSplitEpno() { return splitEpno; }
\r
203 private boolean splitEpno = true;
\r
206 public boolean getExpandTo8() { return expandTo8; }
\r
207 public void setExpandTo8(boolean b) { expandTo8 = b; }
\r
208 private boolean expandTo8 = false;
\r
210 // 番組詳細キャッシュをオンライン取得するかどうか
\r
212 protected boolean isForceLoadDetInfo() { return forceLoadDetInfo; }
\r
214 private boolean forceLoadDetInfo = false;
\r
217 //public void setAbnormal(boolean b) { abnormal = b; }
\r
218 //public boolean getAbnormal() { return abnormal; }
\r
219 //private boolean abnormal = false;
\r
222 /*******************************************************************************
\r
224 ******************************************************************************/
\r
227 /*******************************************************************************
\r
229 ******************************************************************************/
\r
233 * <P>上から順の{@link ProgList}(pcenter/放送局別)->{@link ProgDateList}(pdate/日付別)→{@link ProgDetailList}pdetail/(番組詳細)
\r
235 public ArrayList<ProgList> getCenters() { return(pcenter); }
\r
236 public ArrayList<ProgList> pcenter = new ArrayList<ProgList>();
\r
239 /*******************************************************************************
\r
241 ******************************************************************************/
\r
245 * @see ProgList#Center
\r
246 * @see #sortedcrlist
\r
248 public ArrayList<Center> getCRlist() { return(crlist); }
\r
249 public ArrayList<Center> crlist = new ArrayList<Center>();
\r
253 * @see ProgList#Center
\r
256 public ArrayList<Center> getSortedCRlist() { return(sortedcrlist); }
\r
257 public void setSortedCRlist() {
\r
258 sortedcrlist = new ArrayList<Center>();
\r
259 for (int order=1; order<=crlist.size(); order++) {
\r
260 for (Center center : crlist) {
\r
261 if (center.getOrder() == order) {
\r
262 sortedcrlist.add(center);
\r
268 public ArrayList<Center> sortedcrlist = new ArrayList<Center>();
\r
271 public boolean saveCenter() {
\r
272 String centerListFile = getCenterListFile(getTVProgramId(), getSelectedCode());
\r
273 if ( ! CommonUtils.writeXML(centerListFile,crlist) ) {
\r
274 System.out.println("放送局リストの保存に失敗しました: "+centerListFile+", "+getSelectedCode());
\r
277 System.out.println("放送局リストを保存しました: "+centerListFile+", "+getSelectedCode());
\r
282 protected String getCenterListFile(String id, String code) {
\r
283 return String.format("env%scenter.%s.%s.xml", File.separator, id, code);
\r
286 // 放送局名に一括してフィルタをかける
\r
287 protected void attachChFilters() {
\r
288 for ( Center c : crlist ) {
\r
289 if ( c.getCenterOrig() == null ) {
\r
290 c.setCenterOrig(c.getCenter());
\r
292 c.setCenter(getChConv().get(c.getCenterOrig()));
\r
296 // 放送局名個別にフィルタをかけたい
\r
297 protected String getChName(String chorig) {
\r
298 return getChConv().get(chorig);
\r
302 /*******************************************************************************
\r
304 ******************************************************************************/
\r
309 public ArrayList<AreaCode> getAClist() { return(aclist); }
\r
310 public ArrayList<AreaCode> aclist = new ArrayList<AreaCode>();
\r
315 public String getArea(String code) {
\r
316 for (AreaCode ac : aclist) {
\r
317 if (ac.getCode().equals(code)) {
\r
318 return(ac.getArea());
\r
327 public String getCode(String area) {
\r
328 for (AreaCode ac : aclist) {
\r
329 if (ac.getArea().equals(area)) {
\r
330 return(ac.getCode());
\r
337 * 地域名を指定して選択中の地域を変更し、ついでにその地域コードを返す
\r
338 * @see #getSelectedCode()
\r
340 public String setSelectedAreaByName(String area) {
\r
341 AreaCode ac = null;
\r
342 for (AreaCode atmp : aclist) {
\r
343 if (atmp.getArea().equals(area)) {
\r
344 atmp.setSelected(true);
\r
348 atmp.setSelected(false);
\r
352 return(ac.getCode());
\r
358 * 地域コードを指定して選択中の地域を変更し、ついでにその地域名を返す
\r
359 * @see #getSelectedArea()
\r
361 public String setSelectedAreaByCode(String code) {
\r
362 AreaCode ac = null;
\r
363 for (AreaCode atmp : aclist) {
\r
364 if (atmp.getCode().equals(code)) {
\r
365 atmp.setSelected(true);
\r
369 atmp.setSelected(false);
\r
373 return(ac.getArea());
\r
381 public String getSelectedArea() {
\r
382 for (AreaCode ac : aclist) {
\r
383 if (ac.getSelected() == true) {
\r
384 return(ac.getArea());
\r
387 return(getDefaultArea());
\r
393 public String getSelectedCode() {
\r
394 for (AreaCode ac : aclist) {
\r
395 if (ac.getSelected() == true) {
\r
396 return(ac.getCode());
\r
399 return(getCode(getDefaultArea()));
\r
403 public void saveAreaCode() {
\r
404 if ( CommonUtils.writeXML(getAreaSelectedFile(), aclist) ) {
\r
405 System.out.println(MSGID+"地域リストを保存しました: "+getAreaSelectedFile());
\r
408 System.err.println(ERRID+"地域リストの保存に失敗しました: "+getAreaSelectedFile());
\r
413 protected String getAreaSelectedFile() {
\r
414 return String.format("env%sarea.%s.xml", File.separator, getTVProgramId());
\r
418 /*******************************************************************************
\r
420 ******************************************************************************/
\r
425 protected String doCutDupEpno(String title, String detail) {
\r
426 // タイトルの末尾に話数がついているかな?
\r
427 Matcher md = Pattern.compile("[ ]*?(#[# -・0-9]+|第[0-9]+話)$").matcher(title);
\r
428 if ( ! md.find() ) {
\r
432 ArrayList<String> tnoa = new ArrayList<String>();
\r
434 Matcher me = Pattern.compile("(\\d+)").matcher(md.group(1));
\r
435 while ( me.find() ) {
\r
436 tnoa.add(me.group(1));
\r
438 if ( tnoa.size() == 0 ) {
\r
445 ArrayList<String> dnoa = new ArrayList<String>();
\r
446 Matcher me = Pattern.compile("#[ ]*([0-9]+)|第([0-9]+)話").matcher(detail);
\r
447 while ( me.find() ) {
\r
448 if ( me.group(1) != null ) {
\r
449 dnoa.add(me.group(1));
\r
451 else if ( me.group(2) != null ) {
\r
452 dnoa.add(me.group(2));
\r
455 if ( dnoa.size() == 0 ) {
\r
459 for ( String tno : tnoa ) {
\r
460 for ( String dno : dnoa ) {
\r
461 if ( dno.equals(tno) ) {
\r
467 if ( dnoa.size() == 0 ) {
\r
468 title = md.replaceFirst("");
\r
479 protected void doSplitSubtitle(ProgDetailList pdl) {
\r
481 pdl.splitted_title = doCutDupEpno(pdl.title, pdl.detail); // タイトルと番組詳細中の話数の重複排除
\r
483 String [] d = doSplitEpno(pdl.genre, pdl.splitted_title); // 分離!
\r
485 pdl.splitted_title = pdl.title.substring(0,d[0].length());
\r
487 if ( d[1].length() > 0 ) {
\r
488 // 番組詳細はサブタイトル分離番組詳細へのポインタでいいよ
\r
489 pdl.splitted_detail = d[1]+DETAIL_SEP+pdl.detail;
\r
490 pdl.detail = pdl.splitted_detail.substring(d[1].length()+DETAIL_SEP.length());
\r
493 // サブタイトルが分離されなかったから同じでいいよ
\r
494 pdl.splitted_detail = pdl.detail;
\r
497 // タイトル&番組詳細のキーワード検索情報の設定
\r
500 if (isSplitEpno()) {
\r
501 // サブタイトルを分離するならばそれを考慮した値を使う
\r
502 key_title = pdl.splitted_title;
\r
503 key_detail = pdl.splitted_detail;
\r
506 key_title = pdl.title;
\r
507 key_detail = pdl.detail;
\r
509 pdl.titlePop = TraceProgram.replacePop(key_title);
\r
510 pdl.detailPop = TraceProgram.replacePop(key_detail);
\r
512 pdl.SearchStrKeys = TraceProgram.splitKeys(pdl.titlePop);
\r
516 * サブタイトル分離(Part.11 444-)
\r
518 private String[] doSplitEpno(ProgGenre genre, String title) {
\r
519 if ( genre == ProgGenre.MOVIE || genre == ProgGenre.DOCUMENTARY ) {
\r
520 // 映画とドキュメンタリーは何もしない
\r
523 if ( genre == ProgGenre.DORAMA ) {
\r
524 // ドラマの場合は、"「*」"での分割をしない(土曜ドラマ「タイトル」とかあるため)
\r
525 Matcher mc = Pattern.compile(spep_expr_dorama).matcher(title);
\r
527 return(new String[] { mc.group(1),mc.group(2)+" " });
\r
531 // いきなり「で始まる場合や、タイトル中に『TOKYO「萌」探偵』のように「ほげほげ」を含む場合
\r
532 Matcher mc = Pattern.compile("^([^ ]*「.+?」[^ ]+)(.*)$").matcher(title);
\r
534 Matcher md = Pattern.compile("^[ ]*(.*?)[ ]*?"+spep_expr).matcher(mc.group(2));
\r
536 if ( md.group(1).length() == 0 ) {
\r
537 return(new String[] { mc.group(1),md.group(2)+" " });
\r
540 return(new String[] { mc.group(1)+" "+md.group(1),md.group(2)+" " });
\r
544 return(new String[] { title,"" });
\r
548 mc = Pattern.compile("^(.+?)[ ]*?"+spep_expr).matcher(title);
\r
550 return(new String[] { mc.group(1),mc.group(2)+" " });
\r
554 return(new String[] { title,"" });
\r
558 private static final String spep_expr = "(([<<]?[((##♯第全「][第]?[12345678901234567890一二三四五六七八九十百千]+?[回話章]?|「).*)$";
\r
560 // サブタイトル判定条件(ジャンル=ドラマ専用)
\r
561 private static final String spep_expr_dorama = "^(.+?)[ ]*?(([<<]?[((##♯第全「][第]?[12345678901234567890一二三四五六七八九十百千]+?[回話章]?).*)$";
\r
567 public void abon(ArrayList<String> ngword) {
\r
569 if (ngword.size() == 0) {
\r
572 for ( ProgList p : pcenter ) {
\r
573 for ( ProgDateList c : p.pdate ) {
\r
574 for (ProgDetailList d : c.pdetail) {
\r
575 for (String ngs : ngword) {
\r
576 if (d.title.indexOf(ngs) != -1 || d.detail.indexOf(ngs) != -1) {
\r
588 /*******************************************************************************
\r
590 ******************************************************************************/
\r
592 // キャッシュファイルが有効期限内か確認する
\r
593 public boolean isCacheOld(String fname) {
\r
594 // キャッシュ期限が無期限の場合はDL禁止
\r
595 if (cacheExpired == 0) {
\r
598 // キャッシュ期限があってファイルがない場合はDLしなさい
\r
599 //if (cacheExpired > 0 && fname == null) {
\r
600 if (fname == null) { // あれだ、期限に関係なくファイル名の指定がなきゃDLするしかないよ
\r
603 // 実際のファイルのタイムスタンプを確認する
\r
605 File f = new File(fname);
\r
606 if (f.exists() == true) {
\r
607 long t = System.currentTimeMillis();
\r
608 if (f.lastModified() < (t - cacheExpired*3600000L) ) {
\r
621 catch (Exception e) {
\r
623 System.out.println("Exception: isCacheOld() "+e);
\r
629 /*******************************************************************************
\r
630 * 番組詳細キャッシュ(現在は使われていない)
\r
631 ******************************************************************************/
\r
634 * 番組詳細をオンライン取得するかどうか
\r
637 protected void chkForceLoadDetInfo(boolean force) {
\r
638 File f = new File(getDetCacheFile());
\r
639 if (force == true ||
\r
640 (f.exists() == true && isCacheOld(getDetCacheFile()) == true) ||
\r
641 (f.exists() == false && isCacheOld(null) == true)) {
\r
643 forceLoadDetInfo = true;
\r
645 else if (f.exists()) {
\r
646 // キャッシュファイルが有効なようなので利用します
\r
647 forceLoadDetInfo = false;
\r
651 forceLoadDetInfo = false;
\r
657 protected HashMap<String,String> loadDetCache(){
\r
659 // 設定ファイルが存在していればファイルから
\r
660 File f = new File(getDetCacheFile());
\r
661 if (f.exists() == true) {
\r
662 @SuppressWarnings("unchecked")
\r
663 HashMap<String,String> cache = (HashMap<String, String>) CommonUtils.readXML(getDetCacheFile());
\r
664 if ( cache != null ) {
\r
668 System.out.println(ERRID+"【致命的エラー】番組詳細キャッシュが読み込めません: "+getDetCacheFile());
\r
671 // キャッシュなし&エラーは空配列を返す
\r
672 return new HashMap<String, String>();
\r
677 protected void saveDetCache(HashMap<String,String> cache) {
\r
678 if ( ! CommonUtils.writeXML(getDetCacheFile(), cache) ) {
\r
679 System.err.println(ERRID+"【致命的エラー】番組詳細キャッシュが書き込めません: "+getDetCacheFile());
\r
684 /*******************************************************************************
\r
686 ******************************************************************************/
\r
689 * 日付変更線(29:00)をまたいだら過去のデータはカットする
\r
690 * <B> PassedProgramでは使わない
\r
692 public void refresh() {
\r
694 String critDate = CommonUtils.getDate529(0, true);
\r
695 for ( ProgList p : pcenter ) {
\r
697 for ( ProgDateList c : p.pdate ) {
\r
698 if ( c.Date.compareTo(critDate) >= 0 ) {
\r
703 for ( int j=0; j<i; j++) {
\r
710 * 24時間分番組枠が埋まっているかどうか確認する
\r
712 public String chkComplete() {
\r
713 for ( ProgList p : pcenter ) {
\r
715 for ( ProgDateList c : p.pdate ) {
\r
716 if (c.pdetail.size()<=1) {
\r
717 String msg = "番組枠が存在しません.("+p.Center+","+c.Date+")";
\r
718 System.out.println(msg);
\r
721 if (c.row < 24*60) {
\r
722 String msg = "番組枠が24時間分取得できませんでした.("+p.Center+","+c.Date+","+c.row+")";
\r
723 System.out.println(msg);
\r
727 if (p.pdate.size() < 7) {
\r
728 String msg = "番組表が一週間分取得できませんでした.("+p.Center+")";
\r
729 System.out.println(msg);
\r
740 public void setAccurateDate(ArrayList<ProgDateList> pcenter) {
\r
742 // 先頭のエントリの開始時刻が 5:00 以前の場合
\r
743 for ( ProgDateList pcl : pcenter ) {
\r
744 if (pcl.pdetail.size() <= 0) {
\r
748 ProgDetailList pd = pcl.pdetail.get(0);
\r
749 Matcher ma = Pattern.compile("(\\d\\d):(\\d\\d)").matcher(pd.start);
\r
752 int ahh = Integer.valueOf(ma.group(1));
\r
753 int amm = Integer.valueOf(ma.group(2));
\r
755 GregorianCalendar c = new GregorianCalendar();
\r
756 c.setTime(new Date());
\r
757 c.set(Calendar.HOUR_OF_DAY, ahh);
\r
758 c.set(Calendar.MINUTE, amm);
\r
760 if ( pd.start.compareTo("05:00") < 0 ) {
\r
762 prelength = (5*60+0)-(ahh*60+amm);
\r
763 c.add(Calendar.MINUTE,prelength+pd.length);
\r
764 pd.end = CommonUtils.getTime(c);
\r
766 else if ( pd.start.compareTo("18:00") >= 0 && pd.start.compareTo("24:00") < 0 ) {
\r
768 prelength = (24*60+0)-(ahh*60+amm)+(5*60);
\r
769 c.add(Calendar.MINUTE,prelength+pd.length);
\r
770 pd.end = CommonUtils.getTime(c);
\r
775 for ( ProgDateList pcl : pcenter ) {
\r
777 GregorianCalendar c = CommonUtils.getCalendar(pcl.Date);
\r
779 boolean extend = false;
\r
780 boolean overtwodays = false;
\r
781 for ( int i=0; i<pcl.pdetail.size(); i++ ) {
\r
783 ProgDetailList pdl = pcl.pdetail.get(i);
\r
786 if (pdl.start.compareTo("") == 0) {
\r
792 if ( pdl.start.compareTo("18:00") >= 0 && pdl.start.compareTo("24:00") < 0 ) {
\r
793 // いったい何時間放送するんだよ(--#
\r
794 c.add(Calendar.DAY_OF_MONTH, -1);
\r
795 overtwodays = true;
\r
799 if ( (pdl.start.compareTo("00:00") >= 0 && pdl.start.compareTo("05:00") < 0 && pdl.end.compareTo("05:00") < 0) && extend == false) {
\r
800 c.add(Calendar.DAY_OF_MONTH, 1);
\r
804 pdl.startDateTime = String.format("%s %s", CommonUtils.getDate(c,false), pdl.start);
\r
807 pdl.accurateDate = CommonUtils.getDate(c);
\r
810 if ( overtwodays ) {
\r
811 c.add(Calendar.DAY_OF_MONTH, 1);
\r
812 overtwodays = false;
\r
815 if ( pdl.start.compareTo(pdl.end) > 0 && extend == false) {
\r
816 c.add(Calendar.DAY_OF_MONTH, 1);
\r
821 pdl.endDateTime = String.format("%s %s", CommonUtils.getDate(c,false), pdl.end);
\r
825 // 29時をまたいで同タイトルが続いている場合は同一番組とみなす
\r
826 if ( continueTomorrow ) {
\r
827 for (int w=0; w<pcenter.size()-1; w++) {
\r
828 if (pcenter.get(w).pdetail.size() > 0 && pcenter.get(w+1).pdetail.size() > 0) {
\r
829 ProgDetailList pd1 = pcenter.get(w).pdetail.get(pcenter.get(w).pdetail.size()-1);
\r
830 ProgDetailList pd2 = pcenter.get(w+1).pdetail.get(0);
\r
831 if (pd1.title.equals(pd2.title)) {
\r
833 pd1.endDateTime = pd2.endDateTime;
\r
835 pd2.start = pd1.start;
\r
836 pd2.startDateTime = pd1.startDateTime;
\r
837 pd2.accurateDate = pd1.accurateDate;
\r
839 else if (pd2.title.equals("承前")) {
\r
841 pd1.endDateTime = pd2.endDateTime;
\r
843 pd2.start = pd1.start;
\r
844 pd2.startDateTime = pd1.startDateTime;
\r
845 pd2.accurateDate = pd1.accurateDate;
\r
847 pd2.title = pd1.title;
\r
848 pd2.detail = pd1.detail;
\r
849 pd2.setAddedDetail(pd1.getAddedDetail());
\r
850 pd2.link = pd1.link;
\r
851 pd2.titlePop = pd1.titlePop;
\r
852 pd2.detailPop = pd1.detailPop;
\r
853 pd2.SearchStrKeys = pd1.SearchStrKeys;
\r
854 pd2.nosyobo = pd1.nosyobo;
\r
855 pd2.extension = pd1.extension;
\r
856 pd2.flag = pd1.flag;
\r
857 pd2.genre = pd1.genre;
\r
864 // 以前に取得したデータから当日の取得不能領域のデータを補完する
\r
865 protected void CompensatesPrograms(ArrayList<ProgList> newplist) {
\r
867 for ( ProgList newpl : newplist ) {
\r
869 if ( newpl.enabled != true ) {
\r
874 ArrayList<ProgDateList> newpcla = newpl.pdate;
\r
875 if ( newpcla.size() == 0 ) {
\r
876 // 日付リストが存在しない場合は処理なし
\r
879 ProgDateList newpcl = newpcla.get(0);
\r
881 ArrayList<ProgDetailList> newpdla = newpcl.pdetail;
\r
882 if ( newpdla.size() == 0 ) {
\r
883 // 番組情報が存在しない場合は処理なし
\r
884 if (debug) System.out.println(DBGID+"番組表情報がないので過去ログは参照しない: "+newpcl.Date+" "+newpl.Center);
\r
888 if ( newpdla.get(0).start.length() != 0 ) {
\r
889 // 先頭の番組情報が「番組情報がありません」以外の場合は処理なし
\r
890 if (debug) System.out.println(DBGID+"先頭から有効な情報なので過去ログは参照しない: "+newpcl.Date+" "+newpl.Center+" "+newpdla.get(0).start+" "+newpdla.get(0).title);
\r
894 PassedProgram oldplist = new PassedProgram();
\r
895 if ( ! oldplist.loadByCenter(newpcl.Date, newpl.Center) || oldplist.getProgCount() == 0 ) {
\r
896 // 過去情報が取得できなければ処理なし
\r
897 System.out.println(DBGID+"過去ログに情報はありませんでした");
\r
900 ProgDateList oldpcl = oldplist.pcenter.get(0).pdate.get(0);
\r
903 ArrayList<ProgDetailList> tmppdla = new ArrayList<ProgDetailList>();
\r
904 if ( newpdla.size() == 1 ) {
\r
905 // 「番組情報がありません」しかない場合は全面複写
\r
906 for ( ProgDetailList oldpdl : oldpcl.pdetail ) {
\r
907 tmppdla.add(oldpdl.clone());
\r
912 for ( ProgDetailList oldpdl : oldpcl.pdetail ) {
\r
914 // 過去ログの最初は無条件に追加してよい
\r
915 tmppdla.add(oldpdl.clone());
\r
917 else if ( oldpdl.startDateTime.compareTo(newpdla.get(1).startDateTime) < 0 ) {
\r
918 // 2個目以降は当日の有効情報の前まで(「番組情報がありません」は無条件追加)
\r
919 tmppdla.add(oldpdl.clone());
\r
929 // 先頭の「番組情報はありません」と差し替えて補填
\r
931 for ( int i=0; i<tmppdla.size(); i++ ) {
\r
932 newpdla.add(i,tmppdla.get(i));
\r
939 /*******************************************************************************
\r
941 ******************************************************************************/
\r
946 public void setExtension(String spoexSearchStart, String spoexSearchEnd, boolean spoexLimitation, ArrayList<SearchKey> extKeys) {
\r
948 for ( ProgList p : pcenter ) {
\r
952 String centerPop = TraceProgram.replacePop(p.Center);
\r
954 for ( ProgDateList c : p.pdate ) {
\r
956 boolean poisoned = false;
\r
957 for (ProgDetailList d : c.pdetail) {
\r
959 boolean soi = false;
\r
960 for ( SearchKey k : extKeys ) {
\r
962 boolean isMatch = SearchProgram.isMatchKeyword(k, ((k.getCaseSensitive()==false)?(centerPop):(p.Center)), d);
\r
964 if (k.getInfection().equals("0")) {
\r
979 d.extension = poisoned;
\r
988 protected void doSplitFlags(ProgDetailList pdl, HashMap<String, String> nf) {
\r
990 Matcher md = Pattern.compile("(#1|第1話)\\b").matcher(pdl.title);
\r
992 pdl.flag = ProgFlags.NEW;
\r
995 md = Pattern.compile("([ ]?[<<]新[>>]| 新$| NEW$)").matcher(pdl.title);
\r
997 pdl.flag = ProgFlags.NEW;
\r
998 pdl.title = md.replaceAll("");
\r
1000 md = Pattern.compile("([ ]?[<<]終[>>]| 終$| END$)").matcher(pdl.title);
\r
1001 if ( md.find() ) {
\r
1002 pdl.flag = ProgFlags.LAST;
\r
1003 pdl.title = md.replaceAll("");
\r
1005 md = Pattern.compile("[((]終[))]",Pattern.DOTALL).matcher(pdl.detail);
\r
1006 if ( md.find() ) {
\r
1007 pdl.flag = ProgFlags.LAST;
\r
1009 md = Pattern.compile("^無料≫").matcher(pdl.title);
\r
1010 if ( md.find() ) {
\r
1011 pdl.noscrumble = ProgScrumble.NOSCRUMBLE;
\r
1012 pdl.title = md.replaceAll("");
\r
1015 Pattern pat = Pattern.compile("初放送",Pattern.DOTALL);
\r
1016 if ( pat.matcher(pdl.detail).find() ) {
\r
1017 pdl.addOption(ProgOption.FIRST);
\r
1019 else if ( pat.matcher(pdl.title).find() ) {
\r
1020 pdl.addOption(ProgOption.FIRST);
\r
1023 pat = Pattern.compile("(視聴(..)?制限|[RR]([--]?[11][5588][++]?|指定))",Pattern.DOTALL);
\r
1024 if ( pat.matcher(pdl.detail).find() ) {
\r
1025 pdl.addOption(ProgOption.RATING);
\r
1027 else if ( pat.matcher(pdl.title).find() ) {
\r
1028 pdl.addOption(ProgOption.RATING);
\r
1031 if ( pdl.detail.indexOf("5.1サラウンド") != -1 ) {
\r
1032 pdl.addOption(ProgOption.SURROUND);
\r
1035 HashMap<String, String> xf = new HashMap<String, String>();
\r
1037 String flagExpr = "[\\[[((【](.{1,3})[\\]]))】]";
\r
1038 Matcher mx = Pattern.compile(flagExpr).matcher(pdl.title);
\r
1039 while (mx.find()) {
\r
1040 if (mx.group(1).equals("新") || mx.group(1).equals("新番組")) {
\r
1041 pdl.flag = ProgFlags.NEW;
\r
1043 else if (mx.group(1).equals("終") || mx.group(1).equals("完") || mx.group(1).equals("最終回")) {
\r
1044 pdl.flag = ProgFlags.LAST;
\r
1046 else if (mx.group(1).equals("再")) {
\r
1047 pdl.addOption(ProgOption.REPEAT);
\r
1049 else if (mx.group(1).equals("初")) {
\r
1050 pdl.addOption(ProgOption.FIRST);
\r
1052 else if (mx.group(1).equals("生")) {
\r
1053 pdl.addOption(ProgOption.LIVE);
\r
1056 else if (mx.group(1).equals("二/吹")) {
\r
1057 pdl.addOption(ProgOption.BILINGUAL);
\r
1058 pdl.addOption(ProgOption.STANDIN);
\r
1060 else if (mx.group(1).equals("字") || mx.group(1).equals("字幕") || mx.group(1).equals("字幕版")) {
\r
1061 pdl.addOption(ProgOption.SUBTITLE);
\r
1063 else if (mx.group(1).equals("二")) {
\r
1064 pdl.addOption(ProgOption.BILINGUAL);
\r
1066 else if (mx.group(1).equals("多")) {
\r
1067 pdl.addOption(ProgOption.MULTIVOICE);
\r
1069 else if (mx.group(1).equals("SS") || mx.group(1).equals("5.1")) {
\r
1070 pdl.addOption(ProgOption.SURROUND);
\r
1072 else if (mx.group(1).equals("吹") || mx.group(1).equals("吹替") || mx.group(1).equals("吹替版")) {
\r
1073 pdl.addOption(ProgOption.STANDIN); // (ないよ)
\r
1075 else if (mx.group(1).equals("デ")) {
\r
1076 pdl.addOption(ProgOption.DATA);
\r
1078 else if (mx.group(1).equals("無") || mx.group(1).equals("無料")) {
\r
1079 //pdl.addOption(ProgOption.NOSCRUMBLE);
\r
1080 pdl.noscrumble = ProgScrumble.NOSCRUMBLE;
\r
1083 else if (mx.group(1).matches("^(S|N|B|映|双|解|手|天|英|日|録|HV)$")) {
\r
1085 if ( mx.group(1).equals("日") && ( ! pdl.title.matches(String.format("^(%s)*[((]日[))].*", flagExpr)) && ! pdl.title.matches(".*[\\[[]日[\\]]].*")) ) {
\r
1090 else if (mx.group(1).matches("^(韓|仮|[前後][編篇半]|[月火水木金土]|[0-90-9]+)$")) {
\r
1097 nf.put(mx.group(1),null);
\r
1102 xf.put(mx.group(1), null);
\r
1105 // 認識されたフラグだけ削除する.
\r
1106 String repExpr = "";
\r
1107 for ( String f : xf.keySet() ) {
\r
1108 repExpr += String.format("%s|",f);
\r
1110 if ( repExpr.length() > 0 ) {
\r
1111 repExpr = "[\\[[((【]("+repExpr.substring(0, repExpr.length()-1)+")[\\]]))】]";
\r
1112 pdl.title = pdl.title.replaceAll(repExpr, "");
\r
1116 if ( pdl.title.matches("^特[::].*") ) {
\r
1117 pdl.option.add(ProgOption.SPECIAL);
\r
1118 pdl.title = pdl.title.substring(2);
\r
1120 else if ( pdl.detail.contains("OVA") && ! pdl.detail.contains("+OVA") ) {
\r
1121 pdl.option.add(ProgOption.SPECIAL);
\r
1123 else if ( pdl.detail.contains("未放送") ) {
\r
1124 pdl.option.add(ProgOption.SPECIAL);
\r
1131 protected void setMultiGenre(ProgDetailList pdl, ArrayList<String> genrelist) {
\r
1133 Collections.sort(genrelist);
\r
1135 // ここに入ってこない限り genrelist == null なので対応プラグイン以外ではマルチジャンルは機能しない
\r
1136 pdl.genrelist = new ArrayList<TVProgram.ProgGenre>();
\r
1137 pdl.subgenrelist = new ArrayList<TVProgram.ProgSubgenre>();
\r
1139 String gcode = ProgGenre.NOGENRE.toIEPG();
\r
1140 String subgcode = ProgSubgenre.NOGENRE_ETC.toIEPG();
\r
1142 // マルチジャンルコードを設定する
\r
1143 for ( String gstr : genrelist ) {
\r
1144 // ジャンルコードが複数ある場合は基本的に最初のものを代表にするが、一部例外をもうける(ニュースよりドキュメンタリー優先、など)
\r
1145 // 鯛ナビの一覧で表示されるジャンルは代表のものだけである
\r
1146 String gv = gstr.substring(0,1);
\r
1147 String subgv = gstr.substring(1,2);
\r
1148 if ( gcode.equals(ProgGenre.NOGENRE.toIEPG()) ) {
\r
1152 else if ( gcode.equals(ProgGenre.NEWS.toIEPG()) && gv.equals(ProgGenre.DOCUMENTARY.toIEPG())) {
\r
1157 else if ( gcode.equals(ProgGenre.MUSIC.toIEPG()) && md.group(1).equals(ProgGenre.VARIETY.toIEPG())) {
\r
1158 gcode = md.group(1);
\r
1159 subgcode = md.group(2);
\r
1163 // 3.14.12βでマルチジャンル対応を追加した
\r
1164 // 一覧では代表しか見えないが、検索処理ではすべてのジャンルコードが対象になる
\r
1166 ProgGenre ng = ProgGenre.NOGENRE;
\r
1167 ProgSubgenre nsubg = ProgSubgenre.NOGENRE_ETC;
\r
1168 for ( ProgGenre g : ProgGenre.values() ) {
\r
1169 if ( g.toIEPG().equals(gv) ) {
\r
1171 for ( ProgSubgenre subg : ProgSubgenre.values() ) {
\r
1172 if ( subg.getGenre().equals(g) && subg.toIEPG().equals(subgv) ) {
\r
1180 if ( ng != ProgGenre.NOGENRE ) {
\r
1181 pdl.genrelist.add(ng);
\r
1182 pdl.subgenrelist.add(nsubg);
\r
1185 if ( pdl.genrelist.size() == 0 ) {
\r
1186 pdl.genrelist.add(ProgGenre.NOGENRE);
\r
1187 pdl.subgenrelist.add(ProgSubgenre.NOGENRE_ETC);
\r
1192 for ( ProgGenre g : ProgGenre.values() ) {
\r
1193 if ( g.toIEPG().equals(gcode) ) {
\r
1195 for ( ProgSubgenre subg : ProgSubgenre.values() ) {
\r
1196 if ( subg.getGenre().equals(g) && subg.toIEPG().equals(subgcode) ) {
\r
1197 pdl.subgenre = subg;
\r
1207 /*******************************************************************************
\r
1209 ******************************************************************************/
\r
1211 // Web上から取得してファイルにキャッシュする
\r
1214 public void webToFile(String uri, String fname, String thisEncoding) {
\r
1215 webToFile(uri, null, null, null, fname, thisEncoding);
\r
1219 public void webToFile(String uri, String pstr, String cookie, String referer, String fname, String thisEncoding) {
\r
1222 if ( _webToFile(uri, pstr, cookie, referer, fname, thisEncoding) == true ) {
\r
1225 if ( ++retry > retrycount ) {
\r
1228 System.out.println("wait for retry...");
\r
1229 CommonUtils.milSleep(1000);
\r
1234 private boolean _webToFile(String uri, String pstr, String cookie, String referer, String fname, String thisEncoding) {
\r
1236 HttpURLConnection ucon = null;
\r
1237 BufferedWriter filewriter = null;
\r
1238 BufferedReader filereader = null;
\r
1239 BufferedOutputStream streamwriter = null;
\r
1240 BufferedInputStream streamreader = null;
\r
1242 ucon = _webToXXX(uri,pstr,cookie,referer,thisEncoding);
\r
1243 if (ucon == null) {
\r
1248 if (thisEncoding != null) {
\r
1249 filewriter = new BufferedWriter(new FileWriter(fname+".tmp"));
\r
1250 filereader = new BufferedReader(new InputStreamReader(ucon.getInputStream(), thisEncoding));
\r
1252 while((str = filereader.readLine()) != null){
\r
1253 filewriter.write(str);
\r
1254 filewriter.write("\n");
\r
1256 filewriter.close();
\r
1257 filewriter = null;
\r
1258 filereader.close();
\r
1259 filereader = null;
\r
1262 streamwriter = new BufferedOutputStream(new FileOutputStream(fname+".tmp"));
\r
1263 streamreader = new BufferedInputStream(ucon.getInputStream());
\r
1264 byte[] buf = new byte[65536];
\r
1266 while((len = streamreader.read(buf,0,buf.length)) != -1){
\r
1267 streamwriter.write(buf,0,len);
\r
1269 streamwriter.close();
\r
1270 streamwriter = null;
\r
1271 streamreader.close();
\r
1272 streamreader = null;
\r
1274 ucon.disconnect();
\r
1277 // クローズしてからじゃないと失敗するよ
\r
1280 File o = new File(fname);
\r
1281 if ( o.exists() && ! o.delete() ) {
\r
1282 System.err.println("削除できないよ: "+fname);
\r
1284 File n = new File(fname+".tmp");
\r
1285 if ( ! n.renameTo(o) ) {
\r
1286 System.err.println("リネームできないよ: "+fname+".tmp to "+fname);
\r
1292 catch (Exception e) {
\r
1294 System.out.println("Webアクセスに失敗しました("+uri+"): "+e);
\r
1297 CommonUtils.closing(filewriter);
\r
1298 CommonUtils.closing(filereader);
\r
1299 CommonUtils.closing(streamwriter);
\r
1300 CommonUtils.closing(streamreader);
\r
1301 CommonUtils.closing(ucon);
\r
1306 // Web上から取得してバッファに格納する
\r
1309 public String webToBuffer(String uri, String thisEncoding, boolean nocr) {
\r
1310 return webToBuffer(uri, null, null, null, thisEncoding, nocr);
\r
1314 public String webToBuffer(String uri, String pstr, String cookie, String referer, String thisEncoding, boolean nocr) {
\r
1317 String response = _webToBuffer(uri, pstr, cookie, referer, thisEncoding, nocr);
\r
1318 if ( response != null ) {
\r
1321 if ( ++retry > retrycount ) {
\r
1324 System.out.println("wait for retry...");
\r
1325 CommonUtils.milSleep(1000);
\r
1331 private String _webToBuffer(String uri, String pstr, String cookie, String referer, String thisEncoding, boolean nocr) {
\r
1333 if ( thisEncoding == null ) {
\r
1338 HttpURLConnection ucon = null;
\r
1339 BufferedReader reader = null;
\r
1341 ucon = _webToXXX(uri,pstr,cookie,referer,thisEncoding);
\r
1342 if (ucon == null) {
\r
1347 StringBuilder sb = new StringBuilder();
\r
1348 reader = new BufferedReader(new InputStreamReader(ucon.getInputStream(), thisEncoding));
\r
1350 while((str = reader.readLine()) != null){
\r
1352 if ( ! nocr) sb.append("\n");
\r
1354 return sb.toString();
\r
1356 catch (Exception e) {
\r
1357 System.out.println("Webアクセスに失敗しました("+uri+"): "+e.toString());
\r
1358 //e.printStackTrace();
\r
1361 if ( reader != null ) {
\r
1365 if ( ucon != null ) {
\r
1366 ucon.disconnect();
\r
1371 catch ( Exception e ) {
\r
1378 * File/Bufferの共通部品
\r
1381 private HttpURLConnection _webToXXX(String uri, String pstr, String cookie, String referer, String thisEncoding) {
\r
1383 URL url = new URL(uri);
\r
1384 HttpURLConnection ucon;
\r
1385 if ( getProxy() == null ) {
\r
1386 ucon = (HttpURLConnection)url.openConnection();
\r
1389 ucon = (HttpURLConnection)url.openConnection(getProxy());
\r
1391 ucon.setConnectTimeout(conntout*1000);
\r
1392 ucon.setReadTimeout(readtout*1000);
\r
1394 ucon.addRequestProperty("User-Agent", getUserAgent());
\r
1396 if ( referer != null ) {
\r
1397 ucon.addRequestProperty("Referer", referer);
\r
1400 if ( cookie != null ) {
\r
1401 ucon.addRequestProperty("Cookie", cookie);
\r
1404 // Cookie処理(CookieManagerはなぜうまく動かないんだ…orz)
\r
1405 if ( cookie_cache.size() > 0 ) {
\r
1407 for ( String key : cookie_cache.keySet() ) {
\r
1408 cStr += key+"="+cookie_cache.get(key)+"; ";
\r
1410 ucon.addRequestProperty("Cookie", cStr);
\r
1413 if (pstr != null) {
\r
1414 ucon.setRequestMethod("POST");
\r
1415 ucon.setDoOutput(true);
\r
1416 ucon.setDoInput(true);
\r
1419 ucon.setRequestMethod("GET");
\r
1423 // POSTの場合は別途データを送る
\r
1424 if (pstr != null && thisEncoding != null) {
\r
1425 OutputStream writer = ucon.getOutputStream();
\r
1426 writer.write(pstr.getBytes(thisEncoding));
\r
1432 if ( uri.matches(".*dimora.jp.*") || uri.matches(".*\\.yahoo\\.co\\.jp.*") ) {
\r
1433 List<String> hf = ucon.getHeaderFields().get("Set-Cookie");
\r
1434 if ( hf != null ) {
\r
1435 for ( String rcookie : hf ) {
\r
1436 String[] rc1 = rcookie.split(";",2);
\r
1437 String[] rc2 = rc1[0].split("=",2);
\r
1438 cookie_cache.put(rc2[0], rc2[1]);
\r
1445 } catch (MalformedURLException e) {
\r
1446 e.printStackTrace();
\r
1447 } catch (IOException e) {
\r
1448 e.printStackTrace();
\r
1454 protected String getCookie(String key) {
\r
1455 return cookie_cache.get(key);
\r
1457 protected void addCookie(String key, String val) {
\r
1458 cookie_cache.put(key,val);
\r
1460 protected void delCookie(String key) {
\r
1461 cookie_cache.remove(key);
\r
1463 protected void clrCookie() {
\r
1464 cookie_cache.clear();
\r
1467 // Cookieの情報をキャッシュするよ
\r
1468 private HashMap<String,String> cookie_cache = new HashMap<String, String>();
\r
1471 /*******************************************************************************
\r
1472 * ここから下は該当機能が無効なプラグイン用のダミー
\r
1473 ******************************************************************************/
\r
1475 public String getTVProgramId() { return "DUMMY"; }
\r
1477 public String getDefaultArea() { return "東京"; }
\r
1480 protected String getDetCacheFile() { return ""; }
\r
1483 protected void attachDetails(ArrayList<ProgList> plist, HashMap<String,String> oldcache, HashMap<String,String> newcache) {
\r
1487 // フリーテキストによるオプション指定
\r
1488 public boolean setOptString(String s) { return true; } // ダミー
\r
1489 public String getOptString() { return null; } // ダミー
\r