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 FieldUtils.deepCopy(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 // 分離しない場合でも、番組追跡はサブタイトル抜きでの検索ができるようにしたい
\r
513 pdl.splitted_titlePop = TraceProgram.replacePop(pdl.splitted_title);
\r
517 * サブタイトル分離(Part.11 444-)
\r
519 private String[] doSplitEpno(ProgGenre genre, String title) {
\r
520 if ( genre == ProgGenre.MOVIE || genre == ProgGenre.DOCUMENTARY ) {
\r
521 // 映画とドキュメンタリーは何もしない
\r
524 if ( genre == ProgGenre.DORAMA ) {
\r
525 // ドラマの場合は、"「*」"での分割をしない(土曜ドラマ「タイトル」とかあるため)
\r
526 Matcher mc = Pattern.compile(SPEP_EXPR_DORAMA).matcher(title);
\r
528 return(new String[] { mc.group(1),mc.group(2)+" " });
\r
533 String tit = title;
\r
534 Matcher mani = Pattern.compile("^(\\s*(?:TV|TV)?アニメ\\s*)(.*)$").matcher(title);
\r
535 if ( mani.find() ) {
\r
536 ani = mani.group(1);
\r
537 tit = mani.group(2);
\r
540 // いきなり「で始まる場合や、タイトル中に『TOKYO「萌」探偵』のように「ほげほげ」を含む場合
\r
541 Matcher mc = Pattern.compile("^([^ ]*「.+?」[^ ]+)(.*)$").matcher(tit);
\r
543 Matcher md = Pattern.compile("^[ ]*(.*?)[ ]*?" + SPEP_EXPR).matcher(mc.group(2));
\r
545 if (md.group(1).length() == 0) {
\r
546 return (new String[]{ani+mc.group(1), md.group(2) + " "});
\r
548 return (new String[]{ani+mc.group(1) + " " + md.group(1), md.group(2) + " "});
\r
551 return (new String[]{ani+tit, ""});
\r
554 mc = Pattern.compile("^(.+?)[ ]*?" + SPEP_EXPR).matcher(tit);
\r
556 return (new String[]{ani+mc.group(1), mc.group(2) + " "});
\r
560 return(new String[] { title,"" });
\r
564 private static final String SPEP_EXPR = "(([<<]?[((##♯第全「][第]?[12345678901234567890一二三四五六七八九十百千]+?[回話章]?|「).*)$";
\r
566 // サブタイトル判定条件(ジャンル=ドラマ専用)
\r
567 private static final String SPEP_EXPR_DORAMA = "^(.+?)[ ]*?(([<<]?[((##♯第全「][第]?[12345678901234567890一二三四五六七八九十百千]+?[回話章]?).*)$";
\r
573 public void abon(ArrayList<String> ngword) {
\r
575 if (ngword.size() == 0) {
\r
578 for ( ProgList p : pcenter ) {
\r
579 for ( ProgDateList c : p.pdate ) {
\r
580 for (ProgDetailList d : c.pdetail) {
\r
581 for (String ngs : ngword) {
\r
582 if (d.title.indexOf(ngs) != -1 || d.detail.indexOf(ngs) != -1) {
\r
594 /*******************************************************************************
\r
596 ******************************************************************************/
\r
598 // キャッシュファイルが有効期限内か確認する
\r
599 public boolean isCacheOld(String fname) {
\r
600 // キャッシュ期限が無期限の場合はDL禁止
\r
601 if (cacheExpired == 0) {
\r
604 // キャッシュ期限があってファイルがない場合はDLしなさい
\r
605 //if (cacheExpired > 0 && fname == null) {
\r
606 if (fname == null) { // あれだ、期限に関係なくファイル名の指定がなきゃDLするしかないよ
\r
609 // 実際のファイルのタイムスタンプを確認する
\r
611 File f = new File(fname);
\r
612 if (f.exists() == true) {
\r
613 long t = System.currentTimeMillis();
\r
614 if (f.lastModified() < (t - cacheExpired*3600000L) ) {
\r
627 catch (Exception e) {
\r
629 System.out.println("Exception: isCacheOld() "+e);
\r
635 /*******************************************************************************
\r
636 * 番組詳細キャッシュ(現在は使われていない)
\r
637 ******************************************************************************/
\r
640 * 番組詳細をオンライン取得するかどうか
\r
643 protected void chkForceLoadDetInfo(boolean force) {
\r
644 File f = new File(getDetCacheFile());
\r
645 if (force == true ||
\r
646 (f.exists() == true && isCacheOld(getDetCacheFile()) == true) ||
\r
647 (f.exists() == false && isCacheOld(null) == true)) {
\r
649 forceLoadDetInfo = true;
\r
651 else if (f.exists()) {
\r
652 // キャッシュファイルが有効なようなので利用します
\r
653 forceLoadDetInfo = false;
\r
657 forceLoadDetInfo = false;
\r
663 protected HashMap<String,String> loadDetCache(){
\r
665 // 設定ファイルが存在していればファイルから
\r
666 File f = new File(getDetCacheFile());
\r
667 if (f.exists() == true) {
\r
668 @SuppressWarnings("unchecked")
\r
669 HashMap<String,String> cache = (HashMap<String, String>) CommonUtils.readXML(getDetCacheFile());
\r
670 if ( cache != null ) {
\r
674 System.out.println(ERRID+"【致命的エラー】番組詳細キャッシュが読み込めません: "+getDetCacheFile());
\r
677 // キャッシュなし&エラーは空配列を返す
\r
678 return new HashMap<String, String>();
\r
683 protected void saveDetCache(HashMap<String,String> cache) {
\r
684 if ( ! CommonUtils.writeXML(getDetCacheFile(), cache) ) {
\r
685 System.err.println(ERRID+"【致命的エラー】番組詳細キャッシュが書き込めません: "+getDetCacheFile());
\r
690 /*******************************************************************************
\r
692 ******************************************************************************/
\r
695 * 日付変更線(29:00)をまたいだら過去のデータはカットする
\r
696 * <B> PassedProgramでは使わない
\r
698 public void refresh() {
\r
700 String critDate = CommonUtils.getDate529(0, true);
\r
701 for ( ProgList p : pcenter ) {
\r
703 for ( ProgDateList c : p.pdate ) {
\r
704 if ( c.Date.compareTo(critDate) >= 0 ) {
\r
709 for ( int j=0; j<i; j++) {
\r
716 * 24時間分番組枠が埋まっているかどうか確認する
\r
718 public String chkComplete() {
\r
719 for ( ProgList p : pcenter ) {
\r
721 for ( ProgDateList c : p.pdate ) {
\r
722 if (c.pdetail.size()<=1) {
\r
723 String msg = "番組枠が存在しません.("+p.Center+","+c.Date+")";
\r
724 System.out.println(msg);
\r
727 if (c.row < 24*60) {
\r
728 String msg = "番組枠が24時間分取得できませんでした.("+p.Center+","+c.Date+","+c.row+")";
\r
729 System.out.println(msg);
\r
733 if (p.pdate.size() < 7) {
\r
734 String msg = "番組表が一週間分取得できませんでした.("+p.Center+")";
\r
735 System.out.println(msg);
\r
746 public void setAccurateDate(ArrayList<ProgDateList> pcenter) {
\r
748 // 先頭のエントリの開始時刻が 5:00 以前の場合
\r
749 for ( ProgDateList pcl : pcenter ) {
\r
750 if (pcl.pdetail.size() <= 0) {
\r
754 ProgDetailList pd = pcl.pdetail.get(0);
\r
755 Matcher ma = Pattern.compile("(\\d\\d):(\\d\\d)").matcher(pd.start);
\r
758 int ahh = Integer.valueOf(ma.group(1));
\r
759 int amm = Integer.valueOf(ma.group(2));
\r
761 GregorianCalendar c = new GregorianCalendar();
\r
762 c.setTime(new Date());
\r
763 c.set(Calendar.HOUR_OF_DAY, ahh);
\r
764 c.set(Calendar.MINUTE, amm);
\r
766 if ( pd.start.compareTo("05:00") < 0 ) {
\r
768 prelength = (5*60+0)-(ahh*60+amm);
\r
769 c.add(Calendar.MINUTE,prelength+pd.length);
\r
770 pd.end = CommonUtils.getTime(c);
\r
772 else if ( pd.start.compareTo("18:00") >= 0 && pd.start.compareTo("24:00") < 0 ) {
\r
774 prelength = (24*60+0)-(ahh*60+amm)+(5*60);
\r
775 c.add(Calendar.MINUTE,prelength+pd.length);
\r
776 pd.end = CommonUtils.getTime(c);
\r
781 for ( ProgDateList pcl : pcenter ) {
\r
783 GregorianCalendar c = CommonUtils.getCalendar(pcl.Date);
\r
785 boolean extend = false;
\r
786 boolean overtwodays = false;
\r
787 for ( int i=0; i<pcl.pdetail.size(); i++ ) {
\r
789 ProgDetailList pdl = pcl.pdetail.get(i);
\r
792 if (pdl.start.compareTo("") == 0) {
\r
798 if ( pdl.start.compareTo("18:00") >= 0 && pdl.start.compareTo("24:00") < 0 ) {
\r
799 // いったい何時間放送するんだよ(--#
\r
800 c.add(Calendar.DAY_OF_MONTH, -1);
\r
801 overtwodays = true;
\r
805 if ( (pdl.start.compareTo("00:00") >= 0 && pdl.start.compareTo("05:00") < 0 && pdl.end.compareTo("05:00") < 0) && extend == false) {
\r
806 c.add(Calendar.DAY_OF_MONTH, 1);
\r
810 pdl.startDateTime = String.format("%s %s", CommonUtils.getDate(c,false), pdl.start);
\r
813 pdl.accurateDate = CommonUtils.getDate(c);
\r
816 if ( overtwodays ) {
\r
817 c.add(Calendar.DAY_OF_MONTH, 1);
\r
818 overtwodays = false;
\r
821 if ( pdl.start.compareTo(pdl.end) > 0 && extend == false) {
\r
822 c.add(Calendar.DAY_OF_MONTH, 1);
\r
827 pdl.endDateTime = String.format("%s %s", CommonUtils.getDate(c,false), pdl.end);
\r
831 // 29時をまたいで同タイトルが続いている場合は同一番組とみなす
\r
832 if ( continueTomorrow ) {
\r
833 for (int w=0; w<pcenter.size()-1; w++) {
\r
834 if (pcenter.get(w).pdetail.size() > 0 && pcenter.get(w+1).pdetail.size() > 0) {
\r
835 ProgDetailList pd1 = pcenter.get(w).pdetail.get(pcenter.get(w).pdetail.size()-1);
\r
836 ProgDetailList pd2 = pcenter.get(w+1).pdetail.get(0);
\r
837 if (pd1.title.equals(pd2.title)) {
\r
839 pd1.endDateTime = pd2.endDateTime;
\r
841 pd2.start = pd1.start;
\r
842 pd2.startDateTime = pd1.startDateTime;
\r
843 pd2.accurateDate = pd1.accurateDate;
\r
845 else if (pd2.title.equals("承前")) {
\r
847 pd1.endDateTime = pd2.endDateTime;
\r
849 pd2.start = pd1.start;
\r
850 pd2.startDateTime = pd1.startDateTime;
\r
851 pd2.accurateDate = pd1.accurateDate;
\r
853 pd2.title = pd1.title;
\r
854 pd2.detail = pd1.detail;
\r
855 pd2.setAddedDetail(pd1.getAddedDetail());
\r
856 pd2.link = pd1.link;
\r
857 pd2.titlePop = pd1.titlePop;
\r
858 pd2.detailPop = pd1.detailPop;
\r
859 pd2.nosyobo = pd1.nosyobo;
\r
860 pd2.extension = pd1.extension;
\r
861 pd2.flag = pd1.flag;
\r
862 pd2.genre = pd1.genre;
\r
869 // 以前に取得したデータから当日の取得不能領域のデータを補完する
\r
870 protected void CompensatesPrograms(ArrayList<ProgList> newplist) {
\r
872 for ( ProgList newpl : newplist ) {
\r
874 if ( newpl.enabled != true ) {
\r
879 ArrayList<ProgDateList> newpcla = newpl.pdate;
\r
880 if ( newpcla.size() == 0 ) {
\r
881 // 日付リストが存在しない場合は処理なし
\r
884 ProgDateList newpcl = newpcla.get(0);
\r
886 ArrayList<ProgDetailList> newpdla = newpcl.pdetail;
\r
887 if ( newpdla.size() == 0 ) {
\r
888 // 番組情報が存在しない場合は処理なし
\r
889 if (debug) System.out.println(DBGID+"番組表情報がないので過去ログは参照しない: "+newpcl.Date+" "+newpl.Center);
\r
893 if ( newpdla.get(0).start.length() != 0 ) {
\r
894 // 先頭の番組情報が「番組情報がありません」以外の場合は処理なし
\r
895 if (debug) System.out.println(DBGID+"先頭から有効な情報なので過去ログは参照しない: "+newpcl.Date+" "+newpl.Center+" "+newpdla.get(0).start+" "+newpdla.get(0).title);
\r
899 PassedProgram oldplist = new PassedProgram();
\r
900 if ( ! oldplist.loadByCenter(newpcl.Date, newpl.Center) || oldplist.getProgCount() == 0 ) {
\r
901 // 過去情報が取得できなければ処理なし
\r
902 System.out.println(DBGID+"過去ログに情報はありませんでした");
\r
905 ProgDateList oldpcl = oldplist.pcenter.get(0).pdate.get(0);
\r
908 ArrayList<ProgDetailList> tmppdla = new ArrayList<ProgDetailList>();
\r
909 if ( newpdla.size() == 1 ) {
\r
910 // 「番組情報がありません」しかない場合は全面複写
\r
911 for ( ProgDetailList oldpdl : oldpcl.pdetail ) {
\r
912 tmppdla.add(oldpdl.clone());
\r
917 for ( ProgDetailList oldpdl : oldpcl.pdetail ) {
\r
919 // 過去ログの最初は無条件に追加してよい
\r
920 tmppdla.add(oldpdl.clone());
\r
922 else if ( oldpdl.startDateTime.compareTo(newpdla.get(1).startDateTime) < 0 ) {
\r
923 // 2個目以降は当日の有効情報の前まで(「番組情報がありません」は無条件追加)
\r
924 tmppdla.add(oldpdl.clone());
\r
934 // 先頭の「番組情報はありません」と差し替えて補填
\r
936 for ( int i=0; i<tmppdla.size(); i++ ) {
\r
937 newpdla.add(i,tmppdla.get(i));
\r
943 protected void addEmptyInfo(ProgDateList pcl, String sdat, String edat) {
\r
944 ProgDetailList pdl = new ProgDetailList();
\r
946 pdl.startDateTime = sdat;
\r
947 //pdl.endDateTime = edat;
\r
948 pdl.length = (int)(CommonUtils.getDiffDateTime(sdat, edat)/60000L);
\r
949 pcl.pdetail.add(pdl);
\r
950 pcl.row += pdl.length;
\r
953 /*******************************************************************************
\r
955 ******************************************************************************/
\r
960 public void setExtension(String spoexSearchStart, String spoexSearchEnd, boolean spoexLimitation, ArrayList<SearchKey> extKeys) {
\r
962 for ( ProgList p : pcenter ) {
\r
966 String centerPop = TraceProgram.replacePop(p.Center);
\r
968 for ( ProgDateList c : p.pdate ) {
\r
970 boolean poisoned = false;
\r
971 for (ProgDetailList d : c.pdetail) {
\r
973 boolean soi = false;
\r
974 for ( SearchKey k : extKeys ) {
\r
976 boolean isMatch = SearchProgram.isMatchKeyword(k, ((k.getCaseSensitive()==false)?(centerPop):(p.Center)), d);
\r
978 if (k.getInfection().equals("0")) {
\r
993 d.extension = poisoned;
\r
1002 protected void doSplitFlags(ProgDetailList pdl, HashMap<String, String> nf) {
\r
1004 Matcher md = Pattern.compile("(#1|第1話)\\b").matcher(pdl.title);
\r
1005 if ( md.find() ) {
\r
1006 pdl.flag = ProgFlags.NEW;
\r
1009 md = Pattern.compile("([ ]?[<<]新[>>]| 新$| NEW$)").matcher(pdl.title);
\r
1010 if ( md.find() ) {
\r
1011 pdl.flag = ProgFlags.NEW;
\r
1012 pdl.title = md.replaceAll("");
\r
1014 md = Pattern.compile("([ ]?[<<]終[>>]| 終$| END$)").matcher(pdl.title);
\r
1015 if ( md.find() ) {
\r
1016 pdl.flag = ProgFlags.LAST;
\r
1017 pdl.title = md.replaceAll("");
\r
1019 md = Pattern.compile("[((]終[))]",Pattern.DOTALL).matcher(pdl.detail);
\r
1020 if ( md.find() ) {
\r
1021 pdl.flag = ProgFlags.LAST;
\r
1023 md = Pattern.compile("^無料≫").matcher(pdl.title);
\r
1024 if ( md.find() ) {
\r
1025 pdl.noscrumble = ProgScrumble.NOSCRUMBLE;
\r
1026 pdl.title = md.replaceAll("");
\r
1029 Pattern pat = Pattern.compile("初放送",Pattern.DOTALL);
\r
1030 if ( pat.matcher(pdl.detail).find() ) {
\r
1031 pdl.addOption(ProgOption.FIRST);
\r
1033 else if ( pat.matcher(pdl.title).find() ) {
\r
1034 pdl.addOption(ProgOption.FIRST);
\r
1037 pat = Pattern.compile("(視聴(..)?制限|[RR]([--]?[11][5588][++]?|指定))",Pattern.DOTALL);
\r
1038 if ( pat.matcher(pdl.detail).find() ) {
\r
1039 pdl.addOption(ProgOption.RATING);
\r
1041 else if ( pat.matcher(pdl.title).find() ) {
\r
1042 pdl.addOption(ProgOption.RATING);
\r
1045 if ( pdl.detail.indexOf("5.1サラウンド") != -1 ) {
\r
1046 pdl.addOption(ProgOption.SURROUND);
\r
1049 HashMap<String, String> xf = new HashMap<String, String>();
\r
1051 String flagExpr = "[\\[[((【](.{1,3})[\\]]))】]";
\r
1052 Matcher mx = Pattern.compile(flagExpr).matcher(pdl.title);
\r
1053 while (mx.find()) {
\r
1054 if (mx.group(1).equals("新") || mx.group(1).equals("新番組")) {
\r
1055 pdl.flag = ProgFlags.NEW;
\r
1057 else if (mx.group(1).equals("終") || mx.group(1).equals("完") || mx.group(1).equals("最終回")) {
\r
1058 pdl.flag = ProgFlags.LAST;
\r
1060 else if (mx.group(1).equals("再")) {
\r
1061 pdl.addOption(ProgOption.REPEAT);
\r
1063 else if (mx.group(1).equals("初")) {
\r
1064 pdl.addOption(ProgOption.FIRST);
\r
1066 else if (mx.group(1).equals("生")) {
\r
1067 pdl.addOption(ProgOption.LIVE);
\r
1070 else if (mx.group(1).equals("二/吹")) {
\r
1071 pdl.addOption(ProgOption.BILINGUAL);
\r
1072 pdl.addOption(ProgOption.STANDIN);
\r
1074 else if (mx.group(1).equals("字") || mx.group(1).equals("字幕") || mx.group(1).equals("字幕版")) {
\r
1075 pdl.addOption(ProgOption.SUBTITLE);
\r
1077 else if (mx.group(1).equals("二")) {
\r
1078 pdl.addOption(ProgOption.BILINGUAL);
\r
1080 else if (mx.group(1).equals("多")) {
\r
1081 pdl.addOption(ProgOption.MULTIVOICE);
\r
1083 else if (mx.group(1).equals("SS") || mx.group(1).equals("5.1")) {
\r
1084 pdl.addOption(ProgOption.SURROUND);
\r
1086 else if (mx.group(1).equals("吹") || mx.group(1).equals("吹替") || mx.group(1).equals("吹替版")) {
\r
1087 pdl.addOption(ProgOption.STANDIN); // (ないよ)
\r
1089 else if (mx.group(1).equals("デ")) {
\r
1090 pdl.addOption(ProgOption.DATA);
\r
1092 else if (mx.group(1).equals("無") || mx.group(1).equals("無料")) {
\r
1093 //pdl.addOption(ProgOption.NOSCRUMBLE);
\r
1094 pdl.noscrumble = ProgScrumble.NOSCRUMBLE;
\r
1097 else if (mx.group(1).matches("^(S|N|B|映|双|解|手|天|英|日|録|HV)$")) {
\r
1099 if ( mx.group(1).equals("日") && ( ! pdl.title.matches(String.format("^(%s)*[((]日[))].*", flagExpr)) && ! pdl.title.matches(".*[\\[[]日[\\]]].*")) ) {
\r
1104 else if (mx.group(1).matches("^(韓|仮|[前後][編篇半]|[月火水木金土]|[0-90-9]+)$")) {
\r
1111 nf.put(mx.group(1),null);
\r
1116 xf.put(mx.group(1), null);
\r
1119 // 認識されたフラグだけ削除する.
\r
1120 String repExpr = "";
\r
1121 for ( String f : xf.keySet() ) {
\r
1122 repExpr += String.format("%s|",f);
\r
1124 if ( repExpr.length() > 0 ) {
\r
1125 repExpr = "[\\[[((【]("+repExpr.substring(0, repExpr.length()-1)+")[\\]]))】]";
\r
1126 pdl.title = pdl.title.replaceAll(repExpr, "");
\r
1130 if ( pdl.title.matches("^特[::].*") ) {
\r
1131 pdl.option.add(ProgOption.SPECIAL);
\r
1132 pdl.title = pdl.title.substring(2);
\r
1134 else if ( pdl.detail.contains("OVA") && ! pdl.detail.contains("+OVA") ) {
\r
1135 pdl.option.add(ProgOption.SPECIAL);
\r
1137 else if ( pdl.detail.contains("未放送") ) {
\r
1138 pdl.option.add(ProgOption.SPECIAL);
\r
1145 protected void setMultiGenre(ProgDetailList pdl, ArrayList<String> genrelist) {
\r
1147 Collections.sort(genrelist);
\r
1149 // ここに入ってこない限り genrelist == null なので対応プラグイン以外ではマルチジャンルは機能しない
\r
1150 pdl.genrelist = new ArrayList<TVProgram.ProgGenre>();
\r
1151 pdl.subgenrelist = new ArrayList<TVProgram.ProgSubgenre>();
\r
1153 String gcode = ProgGenre.NOGENRE.toIEPG();
\r
1154 String subgcode = ProgSubgenre.NOGENRE_ETC.toIEPG();
\r
1156 // マルチジャンルコードを設定する
\r
1157 for ( String gstr : genrelist ) {
\r
1158 // ジャンルコードが複数ある場合は基本的に最初のものを代表にするが、一部例外をもうける(ニュースよりドキュメンタリー優先、など)
\r
1159 // 鯛ナビの一覧で表示されるジャンルは代表のものだけである
\r
1160 String gv = gstr.substring(0,1);
\r
1161 String subgv = gstr.substring(1,2);
\r
1162 if ( gcode.equals(ProgGenre.NOGENRE.toIEPG()) ) {
\r
1166 else if ( gcode.equals(ProgGenre.NEWS.toIEPG()) && gv.equals(ProgGenre.DOCUMENTARY.toIEPG())) {
\r
1171 else if ( gcode.equals(ProgGenre.MUSIC.toIEPG()) && md.group(1).equals(ProgGenre.VARIETY.toIEPG())) {
\r
1172 gcode = md.group(1);
\r
1173 subgcode = md.group(2);
\r
1177 // 3.14.12βでマルチジャンル対応を追加した
\r
1178 // 一覧では代表しか見えないが、検索処理ではすべてのジャンルコードが対象になる
\r
1180 ProgGenre ng = ProgGenre.NOGENRE;
\r
1181 ProgSubgenre nsubg = ProgSubgenre.NOGENRE_ETC;
\r
1182 for ( ProgGenre g : ProgGenre.values() ) {
\r
1183 if ( g.toIEPG().equals(gv) ) {
\r
1185 for ( ProgSubgenre subg : ProgSubgenre.values() ) {
\r
1186 if ( subg.getGenre().equals(g) && subg.toIEPG().equals(subgv) ) {
\r
1194 if ( ng != ProgGenre.NOGENRE ) {
\r
1195 pdl.genrelist.add(ng);
\r
1196 pdl.subgenrelist.add(nsubg);
\r
1199 if ( pdl.genrelist.size() == 0 ) {
\r
1200 pdl.genrelist.add(ProgGenre.NOGENRE);
\r
1201 pdl.subgenrelist.add(ProgSubgenre.NOGENRE_ETC);
\r
1206 for ( ProgGenre g : ProgGenre.values() ) {
\r
1207 if ( g.toIEPG().equals(gcode) ) {
\r
1209 for ( ProgSubgenre subg : ProgSubgenre.values() ) {
\r
1210 if ( subg.getGenre().equals(g) && subg.toIEPG().equals(subgcode) ) {
\r
1211 pdl.subgenre = subg;
\r
1221 /*******************************************************************************
\r
1223 ******************************************************************************/
\r
1225 // Web上から取得してファイルにキャッシュする
\r
1228 public void webToFile(String uri, String fname, String thisEncoding) {
\r
1229 webToFile(uri, null, null, null, fname, thisEncoding);
\r
1233 public void webToFile(String uri, String pstr, String cookie, String referer, String fname, String thisEncoding) {
\r
1236 if ( _webToFile(uri, pstr, cookie, referer, fname, thisEncoding) == true ) {
\r
1239 if ( ++retry > retrycount ) {
\r
1242 System.out.println("wait for retry...");
\r
1243 CommonUtils.milSleep(1000);
\r
1248 private boolean _webToFile(String uri, String pstr, String cookie, String referer, String fname, String thisEncoding) {
\r
1250 HttpURLConnection ucon = null;
\r
1251 BufferedWriter filewriter = null;
\r
1252 BufferedReader filereader = null;
\r
1253 BufferedOutputStream streamwriter = null;
\r
1254 BufferedInputStream streamreader = null;
\r
1256 ucon = _webToXXX(uri,pstr,cookie,referer,thisEncoding);
\r
1257 if (ucon == null) {
\r
1262 if (thisEncoding != null) {
\r
1263 filewriter = new BufferedWriter(new FileWriter(fname+".tmp"));
\r
1264 filereader = new BufferedReader(new InputStreamReader(ucon.getInputStream(), thisEncoding));
\r
1266 while((str = filereader.readLine()) != null){
\r
1267 filewriter.write(str);
\r
1268 filewriter.write("\n");
\r
1270 filewriter.close();
\r
1271 filewriter = null;
\r
1272 filereader.close();
\r
1273 filereader = null;
\r
1276 streamwriter = new BufferedOutputStream(new FileOutputStream(fname+".tmp"));
\r
1277 streamreader = new BufferedInputStream(ucon.getInputStream());
\r
1278 byte[] buf = new byte[65536];
\r
1280 while((len = streamreader.read(buf,0,buf.length)) != -1){
\r
1281 streamwriter.write(buf,0,len);
\r
1283 streamwriter.close();
\r
1284 streamwriter = null;
\r
1285 streamreader.close();
\r
1286 streamreader = null;
\r
1288 ucon.disconnect();
\r
1291 // クローズしてからじゃないと失敗するよ
\r
1294 File o = new File(fname);
\r
1295 if ( o.exists() && ! o.delete() ) {
\r
1296 System.err.println("削除できないよ: "+fname);
\r
1298 File n = new File(fname+".tmp");
\r
1299 if ( ! n.renameTo(o) ) {
\r
1300 System.err.println("リネームできないよ: "+fname+".tmp to "+fname);
\r
1306 catch (Exception e) {
\r
1308 System.out.println("Webアクセスに失敗しました("+uri+"): "+e);
\r
1311 CommonUtils.closing(filewriter);
\r
1312 CommonUtils.closing(filereader);
\r
1313 CommonUtils.closing(streamwriter);
\r
1314 CommonUtils.closing(streamreader);
\r
1315 CommonUtils.closing(ucon);
\r
1320 // Web上から取得してバッファに格納する
\r
1323 public String webToBuffer(String uri, String thisEncoding, boolean nocr) {
\r
1324 return webToBuffer(uri, null, null, null, thisEncoding, nocr);
\r
1328 public String webToBuffer(String uri, String pstr, String cookie, String referer, String thisEncoding, boolean nocr) {
\r
1331 String response = _webToBuffer(uri, pstr, cookie, referer, thisEncoding, nocr);
\r
1332 if ( response != null ) {
\r
1335 if ( ++retry > retrycount ) {
\r
1338 System.out.println("wait for retry...");
\r
1339 CommonUtils.milSleep(1000);
\r
1345 private String _webToBuffer(String uri, String pstr, String cookie, String referer, String thisEncoding, boolean nocr) {
\r
1347 if ( thisEncoding == null ) {
\r
1352 HttpURLConnection ucon = null;
\r
1353 BufferedReader reader = null;
\r
1355 ucon = _webToXXX(uri,pstr,cookie,referer,thisEncoding);
\r
1356 if (ucon == null) {
\r
1361 StringBuilder sb = new StringBuilder();
\r
1362 reader = new BufferedReader(new InputStreamReader(ucon.getInputStream(), thisEncoding));
\r
1364 while((str = reader.readLine()) != null){
\r
1366 if ( ! nocr) sb.append("\n");
\r
1368 return sb.toString();
\r
1370 catch (Exception e) {
\r
1371 System.out.println("Webアクセスに失敗しました("+uri+"): "+e.toString());
\r
1372 //e.printStackTrace();
\r
1375 if ( reader != null ) {
\r
1379 if ( ucon != null ) {
\r
1380 ucon.disconnect();
\r
1385 catch ( Exception e ) {
\r
1392 * File/Bufferの共通部品
\r
1395 private HttpURLConnection _webToXXX(String uri, String pstr, String cookie, String referer, String thisEncoding) {
\r
1397 URL url = new URL(uri);
\r
1398 HttpURLConnection ucon;
\r
1399 if ( getProxy() == null ) {
\r
1400 ucon = (HttpURLConnection)url.openConnection();
\r
1403 ucon = (HttpURLConnection)url.openConnection(getProxy());
\r
1405 ucon.setConnectTimeout(conntout*1000);
\r
1406 ucon.setReadTimeout(readtout*1000);
\r
1408 ucon.addRequestProperty("User-Agent", getUserAgent());
\r
1410 if ( referer != null ) {
\r
1411 ucon.addRequestProperty("Referer", referer);
\r
1414 if ( cookie != null ) {
\r
1415 ucon.addRequestProperty("Cookie", cookie);
\r
1418 // Cookie処理(CookieManagerはなぜうまく動かないんだ…orz)
\r
1419 if ( cookie_cache.size() > 0 ) {
\r
1421 for ( String key : cookie_cache.keySet() ) {
\r
1422 cStr += key+"="+cookie_cache.get(key)+"; ";
\r
1424 ucon.addRequestProperty("Cookie", cStr);
\r
1427 if (pstr != null) {
\r
1428 ucon.setRequestMethod("POST");
\r
1429 ucon.setDoOutput(true);
\r
1430 ucon.setDoInput(true);
\r
1433 ucon.setRequestMethod("GET");
\r
1437 // POSTの場合は別途データを送る
\r
1438 if (pstr != null && thisEncoding != null) {
\r
1439 OutputStream writer = ucon.getOutputStream();
\r
1440 writer.write(pstr.getBytes(thisEncoding));
\r
1446 if ( uri.matches(".*dimora.jp.*") || uri.matches(".*\\.yahoo\\.co\\.jp.*") ) {
\r
1447 List<String> hf = ucon.getHeaderFields().get("Set-Cookie");
\r
1448 if ( hf != null ) {
\r
1449 for ( String rcookie : hf ) {
\r
1450 String[] rc1 = rcookie.split(";",2);
\r
1451 String[] rc2 = rc1[0].split("=",2);
\r
1452 cookie_cache.put(rc2[0], rc2[1]);
\r
1459 } catch (MalformedURLException e) {
\r
1460 e.printStackTrace();
\r
1461 } catch (IOException e) {
\r
1462 e.printStackTrace();
\r
1468 protected String getCookie(String key) {
\r
1469 return cookie_cache.get(key);
\r
1471 protected void addCookie(String key, String val) {
\r
1472 cookie_cache.put(key,val);
\r
1474 protected void delCookie(String key) {
\r
1475 cookie_cache.remove(key);
\r
1477 protected void clrCookie() {
\r
1478 cookie_cache.clear();
\r
1481 // Cookieの情報をキャッシュするよ
\r
1482 private HashMap<String,String> cookie_cache = new HashMap<String, String>();
\r
1485 /*******************************************************************************
\r
1486 * ここから下は該当機能が無効なプラグイン用のダミー
\r
1487 ******************************************************************************/
\r
1489 public String getTVProgramId() { return "DUMMY"; }
\r
1491 public String getDefaultArea() { return "東京"; }
\r
1494 protected String getDetCacheFile() { return ""; }
\r
1497 protected void attachDetails(ArrayList<ProgList> plist, HashMap<String,String> oldcache, HashMap<String,String> newcache) {
\r
1501 // フリーテキストによるオプション指定
\r
1502 public boolean setOptString(String s) { return true; } // ダミー
\r
1503 public String getOptString() { return null; } // ダミー
\r