• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Frequently used words (click to add to your profile)

javaandroidc++linuxc#objective-ccocoa誰得qtrubybathyscaphegamewindowspythonphpguic翻訳omegattwitterframeworkbtronarduinovb.net計画中(planning stage)directxpreviewertestゲームエンジンdom

タイニー番組ナビゲータ本体


Commit MetaInfo

Revisionc99c5aaed5ea49acd0e9955d09a07bb249ac7a99 (tree)
Time2019-12-03 13:44:22
AuthorMasahiko Kimura <mkimura@u01....>
CommiterMasahiko Kimura

Log Message

Ver.3.22.18β+1.12.5 (2019/12/2)
1. [リスト形式]「しょぼかる」ノードが表示されない問題の対応(更新後env\lninfolist.xmlを削除する必要がある)

Ver.3.22.18β+1.12.4 (2019/12/2)
1. [予約リスト]不正な予約データがあると CommonUtils.getStartEndList() で例外が発生する問題の対応
2. [レコーダ設定]DBR-M2008への暫定対応(タイトル編集、削除、フォルダ移動は未検証)

Change Summary

Incremental Difference

--- a/TinyBannavi/src/tainavi/AbsRecorderSettingView.java
+++ b/TinyBannavi/src/tainavi/AbsRecorderSettingView.java
@@ -117,6 +117,7 @@ public abstract class AbsRecorderSettingView extends JScrollPane {
117117 private static final int TEXT_WIDTH = CCLABEL_WIDTH*2+SEP_WIDTH;
118118
119119 private static final int BUTTON_WIDTH = 75;
120+ private static final int BUTTON_X = SEP_WIDTH+LABEL_WIDTH+SEP_WIDTH+TEXT_WIDTH+SEP_WIDTH+TEXT_WIDTH+SEP_WIDTH+150;
120121
121122 private static final int UPDATE_WIDTH = 250;
122123 private static final int HINT_WIDTH = 750;
@@ -1006,14 +1007,14 @@ public abstract class AbsRecorderSettingView extends JScrollPane {
10061007
10071008 y+=(table_h+SEP_HEIGHT);
10081009 int yz = y;
1009- CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderup("上へ"), BUTTON_WIDTH, PARTS_HEIGHT, table_w-BUTTON_WIDTH, yz);
1010- CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderdown("下へ"), BUTTON_WIDTH, PARTS_HEIGHT, table_w-BUTTON_WIDTH, yz+=(PARTS_HEIGHT+SEP_HEIGHT_NALLOW));
1010+ CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderup("上へ"), BUTTON_WIDTH, PARTS_HEIGHT, BUTTON_X, yz);
1011+ CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderdown("下へ"), BUTTON_WIDTH, PARTS_HEIGHT, BUTTON_X, yz+=(PARTS_HEIGHT+SEP_HEIGHT_NALLOW));
10111012
10121013 yz += (PARTS_HEIGHT+SEP_HEIGHT_NALLOW)*3;
10131014
1014- CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderadd("登録"), BUTTON_WIDTH, PARTS_HEIGHT, table_w-BUTTON_WIDTH, yz);
1015- CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderupd("置換"), BUTTON_WIDTH, PARTS_HEIGHT, table_w-BUTTON_WIDTH, yz+=(PARTS_HEIGHT+SEP_HEIGHT_NALLOW));
1016- CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderdel("削除"), BUTTON_WIDTH, PARTS_HEIGHT, table_w-BUTTON_WIDTH, yz+=(PARTS_HEIGHT+SEP_HEIGHT_NALLOW));
1015+ CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderadd("登録"), BUTTON_WIDTH, PARTS_HEIGHT, BUTTON_X, yz);
1016+ CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderupd("置換"), BUTTON_WIDTH, PARTS_HEIGHT, BUTTON_X, yz+=(PARTS_HEIGHT+SEP_HEIGHT_NALLOW));
1017+ CommonSwingUtils.putComponentOn(jPanel_recorder, getJButton_recorderdel("削除"), BUTTON_WIDTH, PARTS_HEIGHT, BUTTON_X, yz+=(PARTS_HEIGHT+SEP_HEIGHT_NALLOW));
10171018
10181019 y+=(PARTS_HEIGHT+SEP_HEIGHT);
10191020 CommonSwingUtils.putComponentOn(jPanel_recorder, getJLabel_recordertype("レコーダ機種"), LABEL_WIDTH, PARTS_HEIGHT, SEP_WIDTH, y);
--- a/TinyBannavi/src/tainavi/CommonUtils.java
+++ b/TinyBannavi/src/tainavi/CommonUtils.java
@@ -309,7 +309,15 @@ public class CommonUtils {
309309 //boolean isOverDay = (r.getAhh().compareTo(r.getZhh()) > 0); // 日付をまたいだ
310310 //boolean isLateNight = (adjLateNight == false && ( ptnid >= 7 && ptnid <= 10 ) && isLateNight(r.getAhh())); // 深夜帯(毎日&帯予約のみ利用)
311311
312- int len = Integer.valueOf(getRecMin(r.getAhh(), r.getAmm(), r.getZhh(), r.getZmm()));
312+ int len = 0;
313+ try{
314+ len = Integer.valueOf(getRecMin(r.getAhh(), r.getAmm(), r.getZhh(), r.getZmm()));
315+ }
316+ catch(NumberFormatException e){
317+ System.err.println("[ERROR] at getStartEndList(): "+r.getAhh()+","+r.getAmm()+","+r.getZhh()+","+r.getZmm());
318+ e.printStackTrace();
319+ return;
320+ }
313321
314322 // 予約パターンによりけり対象となる日付は増えたり増えたり
315323 if (ptnid == 11) {
--- a/TinyBannavi/src/tainavi/ListedNodeInfoList.java
+++ b/TinyBannavi/src/tainavi/ListedNodeInfoList.java
@@ -118,6 +118,7 @@ public class ListedNodeInfoList extends ArrayList<ListColumnInfo> implements Clo
118118 {true, "ジャンル別", idx++},
119119 {true, "放送局別", idx++},
120120 {true, "延長警告管理", idx++},
121+ {true, "しょぼかる", idx++},
121122 };
122123 for (int i=0; i<o.length; i++) {
123124 ListColumnInfo cb = new ListColumnInfo();
--- a/TinyBannavi/src/tainavi/VersionInfo.java
+++ b/TinyBannavi/src/tainavi/VersionInfo.java
@@ -5,7 +5,7 @@ import java.util.regex.Pattern;
55
66
77 public class VersionInfo {
8- private static final String Version = "タイニー番組ナビゲータ for DBR-T2007 3.22.18β+1.12.3";
8+ private static final String Version = "タイニー番組ナビゲータ for DBR-T2007 3.22.18β+1.12.5";
99
1010 private static final String OSname = System.getProperty("os.name");
1111 private static final String OSvers = System.getProperty("os.version");
--- /dev/null
+++ b/TinyBannavi/src/tainavi/pluginrec/PlugIn_RecDBR_M2008.java
@@ -0,0 +1,2831 @@
1+package tainavi.pluginrec;
2+
3+import java.io.File;
4+import java.io.UnsupportedEncodingException;
5+import java.net.Authenticator;
6+import java.net.URLEncoder;
7+import java.text.ParseException;
8+import java.text.SimpleDateFormat;
9+import java.util.ArrayList;
10+import java.util.Comparator;
11+import java.util.Date;
12+import java.util.HashMap;
13+import java.util.Locale;
14+import java.util.regex.Matcher;
15+import java.util.regex.Pattern;
16+
17+import tainavi.AribCharMap;
18+import tainavi.ChannelCode;
19+import tainavi.ChapterInfo;
20+import tainavi.CommonUtils;
21+import tainavi.DeviceInfo;
22+import tainavi.GetRDStatus;
23+import tainavi.HDDRecorder;
24+import tainavi.HDDRecorderUtils;
25+import tainavi.ReserveList;
26+import tainavi.TVProgram.ProgGenre;
27+import tainavi.TextValueSet;
28+import tainavi.TitleInfo;
29+import tainavi.TraceProgram;
30+
31+/**
32+ * REGZA DBR-T2007用のレコーダプラグインです。
33+ * @author original:tmskmr
34+ * @version x.xx.xx
35+ */
36+public class PlugIn_RecDBR_M2008 extends HDDRecorderUtils implements HDDRecorder,Cloneable {
37+ public PlugIn_RecDBR_M2008() {
38+ super();
39+ this.setTunerNum(3);
40+ setSettingFixed();
41+ }
42+
43+ public PlugIn_RecDBR_M2008 clone() {
44+ return (PlugIn_RecDBR_M2008) super.clone();
45+ }
46+
47+ private static final String thisEncoding = "UTF-8";
48+
49+ /*******************************************************************************
50+ * 種族の特性
51+ ******************************************************************************/
52+
53+ // 種族の特性
54+ @Override
55+ public String getRecorderId() { return "REGZA DBR-M2008"; }
56+ @Override
57+ public RecType getType() { return RecType.RECORDER; }
58+
59+ // chvalueを使っていいよ
60+ @Override
61+ public boolean isChValueAvailable() { return true; }
62+ // CHコードは入力しなくていい
63+ @Override
64+ public boolean isChCodeNeeded() { return false; }
65+
66+ // 録画タイトルに対応している
67+ @Override
68+ public boolean isTitleListSupported() { return true; }
69+
70+ // 持ち出しに対応している
71+ @Override
72+ public boolean isPortableSupported() { return true; }
73+
74+ // フォルダー作成に対応している
75+ @Override
76+ public boolean isFolderCreationSupported() { return true; }
77+
78+ /*******************************************************************************
79+ * 予約ダイアログなどのテキストのオーバーライド
80+ ******************************************************************************/
81+
82+ @Override
83+ public String getChDatHelp() { return
84+ "「レコーダの放送局名」はネットdeナビの録画予約一覧で表示される「CH」の欄の値を設定してください。\n"+
85+ "予約一覧取得が正常に完了していれば設定候補がコンボボックスで選択できるようになります。"
86+ ;
87+ }
88+
89+ /*******************************************************************************
90+ * 定数
91+ ******************************************************************************/
92+
93+ protected final String MSGID = "["+getRecorderId()+"] ";
94+ protected final String ERRID = "[ERROR]"+MSGID;
95+ protected final String DBGID = "[DEBUG]"+MSGID;
96+
97+ protected final String NETWORK_UVD = "0";
98+ protected final String NETWORK_BSD = "1";
99+ protected final String NETWORK_CSD = "2";
100+ protected final String NETWORK_L1 = "3";
101+ protected final String NETWORK_L2 = "4";
102+ protected final String NETWORK_L3 = "5";
103+ protected final String NETWORK_L4 = "6";
104+ protected final String NETWORK_NET = "7";
105+
106+ protected final String CHTYPE_UVD = "uvd";
107+ protected final String CHTYPE_BSD = "bsd";
108+ protected final String CHTYPE_CSD = "csd";
109+ protected final String CHTYPE_L1 = "L1";
110+ protected final String CHTYPE_L2 = "L2";
111+ protected final String CHTYPE_L3 = "L3";
112+ protected final String CHTYPE_L4 = "L4";
113+ protected final String CHTYPE_NET = "NET";
114+
115+ protected final String CHPREFIX_BS = "BS";
116+ protected final String CHPREFIX_CS = "CS";
117+ protected final String CH_L1 = "L1";
118+ protected final String CH_L2 = "L2";
119+ protected final String CH_L3 = "L3";
120+ protected final String CH_L4 = "L4";
121+ protected final String CH_NET = "NET";
122+
123+ protected final String OPTION_DATETIME = "1";
124+ protected final String OPTION_PROGRAM = "2";
125+
126+ protected final String REPEAT_NONE = "0";
127+
128+ protected final int RPTPTN_ID_TUE2SAT = 12;
129+
130+ protected final String EXEC_YES = "1";
131+ protected final String EXEC_NO = "0";
132+
133+ protected final String BRANCH_NO = "0";
134+ protected final String BRANCH_YES = "1";
135+
136+ protected final String DEVICE_HDD = "0";
137+ protected final String DEVICE_DISC = "1";
138+ protected final String FOLDER_NONE = "0";
139+ protected final String FOLDER_NAME_NONE = "指定なし";
140+
141+ protected final String RECMODE_DR = "0";
142+ protected final String RECMODE_NAME_DR = "[DR]";
143+
144+ protected final String MOCHIDASHI_NONE = "0";
145+
146+ protected final String RESULT_OK = "0";
147+ protected final String RESULT_OTHER = "1";
148+ protected final String RESULT_BUSY = "2";
149+ protected final String RESULT_INVALID_TITLE = "17";
150+
151+ protected final String ERRMSG_NORESPONSE = "レコーダーが反応しません";
152+ protected final String ERRMSG_INVALIDRESPONSE = "レコーダーからの応答が不正です。";
153+ protected final String ERRMSG_USB_BUSY = "USB HDDが使用中です。";
154+
155+ protected final double MIN_PER_MB = 171.777;
156+
157+ protected final String TITLE_NOCHANGED = "__NETdeNAVI_TitleNameNotChanged__";
158+
159+ protected final Boolean test = false;
160+
161+ /*******************************************************************************
162+ * CHコード設定、エラーメッセージ
163+ ******************************************************************************/
164+
165+ public ChannelCode getChCode() {
166+ return cc;
167+ }
168+
169+ private ChannelCode cc = new ChannelCode(getRecorderId());
170+
171+ protected void setErrmsg(String s) { errmsg = s; }
172+
173+ public String getErrmsg() {
174+ return(errmsg.replaceAll("\\\\r\\\\n", ""));
175+ }
176+
177+ private String errmsg = "";
178+
179+ /*******************************************************************************
180+ * 部品
181+ ******************************************************************************/
182+
183+ private GetRDStatus gs = new GetRDStatus();
184+
185+ private String rsvedFile = "";
186+ private String folderTFile = "";
187+ private String titleFile = "";
188+ private String devinfoTFile = "";
189+ private ArrayList<TextValueSet> tvsBranch = new ArrayList<TextValueSet>();
190+ private ArrayList<TextValueSet> tvsPatternCode = new ArrayList<TextValueSet>();
191+ private ArrayList<TextValueSet> tvsPatternName = new ArrayList<TextValueSet>();
192+
193+ /*******************************************************************************
194+ * コンストラクタ
195+ ******************************************************************************/
196+
197+ /*******************************************************************************
198+ * チャンネルリモコン機能
199+ ******************************************************************************/
200+
201+ /*******************************************************************************
202+ * レコーダーから各種設定情報を取得する
203+ ******************************************************************************/
204+ @Override
205+ public boolean GetRdSettings(boolean force) {
206+
207+ System.out.println("レコーダの各種設定情報を取得します.");
208+
209+ String myFileId = getIPAddr()+"_"+getPortNo()+"_"+getRecorderId();
210+
211+ String deviceTFile = "env/device."+myFileId+".xml";
212+ devinfoTFile = "env/devinfo."+myFileId+".xml";
213+ folderTFile = "env/folders."+myFileId+".xml";
214+ String chValueTFile = "env/chvalue."+myFileId+".xml";
215+ String chTypeTFile = "env/chtype."+myFileId+".xml";
216+ String branchTFile = "env/branch."+myFileId+".xml";
217+
218+ // 固定の各種設定情報を初期化する
219+ setSettingFixed();
220+
221+ File f = new File(deviceTFile);
222+ if ( !force){
223+ if (!f.exists())
224+ return(false);
225+
226+ // キャッシュから読み出し(録画設定ほか)
227+ device = TVSload(deviceTFile);
228+ setDeviceInfos(DeviceInfosFromFile(devinfoTFile));
229+ folder = TVSload(folderTFile);
230+ chvalue = TVSload(chValueTFile);
231+ chtype = TVSload(chTypeTFile);
232+ tvsBranch = TVSload(branchTFile);
233+
234+ // なぜか設定ファイルが空になっている場合があるので、その際は再取得する
235+ if (device.size() > 0 && chvalue.size() > 0 && chtype.size() > 0 && tvsBranch.size() > 0) {
236+ return(true);
237+ }
238+ }
239+
240+ // 各種設定情報をレコーダから取得する
241+ if (!setSettingVariable()){
242+ return (false);
243+ }
244+
245+ TVSsave(device, deviceTFile);
246+ saveDeviceInfos();
247+ saveFolders();
248+ TVSsave(chvalue, chValueTFile);
249+ TVSsave(chtype, chTypeTFile);
250+ TVSsave(tvsBranch, branchTFile);
251+
252+ return(true);
253+ }
254+
255+ /*******************************************************************************
256+ * レコーダーから予約一覧を取得する
257+ ******************************************************************************/
258+
259+ public boolean GetRdReserve(boolean force)
260+ {
261+ System.out.println("レコーダから予約一覧を取得します("+force+"): "+getRecorderId()+"("+getIPAddr()+":"+getPortNo()+")");
262+
263+ errmsg = "";
264+
265+ //
266+ String myFileId = getIPAddr()+"_"+getPortNo()+"_"+getRecorderId();
267+
268+ rsvedFile = "env/reserved."+myFileId+".xml";
269+
270+ File f = new File(rsvedFile);
271+ if ( !force && f.exists()) {
272+ // キャッシュから読み出し(予約一覧)
273+ setReserves(ReservesFromFile(rsvedFile));
274+
275+ // なぜか設定ファイルが空になっている場合があるので、その際は再取得する
276+ if (getReserves().size() > 0) {
277+ return(true);
278+ }
279+ }
280+
281+ // 録画予約の一覧をレコーダから取得する
282+ ArrayList<ReserveList> ra = getReserveList();
283+ if (ra == null){
284+ return(false);
285+ }
286+
287+ setReserves(ra);
288+
289+ // キャッシュに保存
290+ ReservesToFile(getReserves(), rsvedFile);
291+
292+ // 取得した情報の表示
293+ if (getDebug()){
294+ System.out.println("---Reserved List Start---");
295+ for ( int i = 0; i<getReserves().size(); i++ ) {
296+ // 詳細情報の取得
297+ ReserveList e = getReserves().get(i);
298+ System.out.println(String.format("[%s] %s\t%s\t%s\t%s:%s\t%s:%s\t%sm\t%s\t%s(%s)\t%s\t%s",
299+ (i+1), e.getId(), e.getRec_pattern(), e.getRec_nextdate(), e.getAhh(), e.getAmm(), e.getZhh(), e.getZmm(), e.getRec_min(), e.getRec_mode(), e.getTitle(), e.getTitlePop(), e.getChannel(), e.getCh_name()));
300+ }
301+ System.out.println("---Reserved List End---");
302+ }
303+
304+ return(true);
305+ }
306+
307+ /*******************************************************************************
308+ * 予約詳細情報の取得
309+ ******************************************************************************/
310+ @Override
311+ public boolean isThereAdditionalDetails() {
312+ return true;
313+ }
314+
315+ /*
316+ * 予約の詳細情報を取得する
317+ */
318+ @Override
319+ public boolean GetRdReserveDetails(){
320+ int rno = 0;
321+ ArrayList<ReserveList> ra = getReserves();
322+ for (ReserveList entry : ra) {
323+
324+ reportProgress("+番組詳細を取得します("+rno+"/"+ra.size()+").");
325+ getReserveDetail(entry);
326+
327+ // 放送局名変換
328+ entry.setCh_name(getChCode().getCH_REC2WEB(entry.getChannel()));
329+
330+ // TS->DR
331+ translateAttributeTuner(entry);
332+
333+ // タイトル自動補完フラグなど本体からは取得できない情報を引き継ぐ
334+ copyAttributesT2007(entry, getReserves());
335+
336+ rno++;
337+ }
338+
339+ // キャッシュに保存
340+ ReservesToFile(getReserves(), rsvedFile);
341+
342+ return(true);
343+ }
344+
345+ /*******************************************************************************
346+ * 新規予約
347+ ******************************************************************************/
348+ @Override
349+ public boolean PostRdEntry(ReserveList r) {
350+ errmsg = "";
351+
352+ String chcode = cc.getCH_WEB2CODE(r.getCh_name());
353+ if (chcode == null) {
354+ errmsg = "【警告】Web番組表の放送局名「"+r.getCh_name()+"」をCHコードに変換できません。CHコード設定を修正してください。" ;
355+ System.out.println(errmsg);
356+ return(false);
357+ }
358+
359+ // RDから予約登録画面の初期情報を取り出す。これを呼ばないと ERR_EXCLUSIVEエラーになる
360+ if ( !loadDialogInitData("0")){
361+ return(false);
362+ }
363+
364+ // RDへの情報作成
365+ String pstr = createPostData(r, "");
366+
367+ // RDへ情報送信
368+ reportProgress(MSGID+"レコーダーに新規予約を要求します.");
369+ String [] d = reqPOST("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/DoReserve.php", pstr, null, thisEncoding);
370+// String header = d[0];
371+ String response = d[1];
372+
373+ // 登録結果の確認
374+ if ( !checkReserveResponse(r, response) ){
375+ return(false);
376+ }
377+
378+ // 予約情報の調整
379+ adjustReserve(r);
380+
381+ // 予約リストを更新
382+ getReserves().add(r);
383+
384+ // キャッシュに保存
385+ ReservesToFile(getReserves(), rsvedFile);
386+
387+ reportProgress(MSGID+"正常に登録できました。");
388+
389+ return(true);
390+ }
391+
392+ /*******************************************************************************
393+ * 予約更新
394+ ******************************************************************************/
395+ @Override
396+ public boolean UpdateRdEntry(ReserveList o, ReserveList r) {
397+ errmsg = "";
398+
399+ String chcode = cc.getCH_WEB2CODE(r.getCh_name());
400+ if (chcode == null) {
401+ errmsg = "【警告】Web番組表の放送局名「"+r.getCh_name()+"」をCHコードに変換できません。CHコード設定を修正してください。" ;
402+ System.out.println(errmsg);
403+ return(false);
404+ }
405+
406+ String id = o.getId();
407+
408+ // RDから予約登録画面の初期情報を取り出す。これを呼ばないと ERR_EXCLUSIVEエラーになる
409+ if ( !loadDialogInitData(id) ){
410+ return(false);
411+ }
412+
413+ // RDへの情報作成
414+ String pstr = createPostData(r, id);
415+
416+ // RDへ情報送信
417+ reportProgress(MSGID+"レコーダーに予約更新を要求します.");
418+ String [] d = reqPOST("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/DoReserve.php", pstr, null, thisEncoding);
419+// String header = d[0];
420+ String response = d[1];
421+
422+ // 登録結果の確認
423+ if ( !checkReserveResponse(r, response) ){
424+ return(false);
425+ }
426+
427+ // 予約情報の調整
428+ adjustReserve(r);
429+
430+ // 情報置き換え
431+ getReserves().remove(o);
432+ getReserves().add(r);
433+
434+ // キャッシュに保存
435+ ReservesToFile(getReserves(), rsvedFile);
436+
437+ reportProgress(MSGID+"正常に更新できました。");
438+
439+ return(true);
440+ }
441+
442+ /*******************************************************************************
443+ * 予約削除
444+ ******************************************************************************/
445+ @Override
446+ public ReserveList RemoveRdEntry(String delid) {
447+ errmsg = "";
448+
449+ // 削除対象を探す
450+ ReserveList r = null;
451+ for ( ReserveList reserve : getReserves() ) {
452+ if (reserve.getId().equals(delid)) {
453+ r = reserve;
454+ break;
455+ }
456+ }
457+ if (r == null) {
458+ return(null);
459+ }
460+
461+ // RDから予約登録画面の初期情報を取り出す。これを呼ばないと ERR_EXCLUSIVEエラーになる
462+ if ( !loadDialogInitData(delid)){
463+ return(null);
464+ }
465+
466+ // RDへの情報作成
467+ String pstr = createPostData(r, delid);
468+
469+ // RDへ情報送信
470+ reportProgress(MSGID+"レコーダーに予約削除を要求します.");
471+ String [] d = reqPOST("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/DeleteReserve.php", pstr, null, thisEncoding);
472+// String header = d[0];
473+ String response = d[1];
474+
475+ if ( !checkGeneralResponse( "予約削除", response) ){
476+ return(null);
477+ }
478+
479+ // 予約リストを更新
480+ getReserves().remove(r);
481+
482+ // キャッシュに保存
483+ ReservesToFile(getReserves(), rsvedFile);
484+
485+ reportProgress(MSGID+"正常に削除できました。");
486+ System.out.printf("\n<<< Message from RD >>> \"%s\"\n\n", "正常に削除できました。");
487+ return(r);
488+ }
489+
490+ /*******************************************************************************
491+ * フォルダー作成
492+ ******************************************************************************/
493+ @Override
494+ public boolean CreateRdFolder(String device_id, String folder_name) {
495+ return setFolderName( device_id, null, folder_name );
496+ }
497+
498+ /*******************************************************************************
499+ * フォルダー名更新
500+ ******************************************************************************/
501+ @Override
502+ public boolean UpdateRdFolderName(String device_id, String folder_id, String folder_name) {
503+ return setFolderName( device_id, folder_id, folder_name );
504+ }
505+
506+ /*******************************************************************************
507+ * フォルダー削除
508+ ******************************************************************************/
509+ @Override
510+ public boolean RemoveRdFolder(String device_id, String fol_id) {
511+ String action = "フォルダ削除";
512+ String folder_id = extractFolderID(fol_id);
513+
514+ String pstr =
515+ "drive_id=" + device_id + "&" +
516+ "folder_id=" + folder_id;
517+
518+ for (int n=0; n<3; n++){
519+ // おまじない
520+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
521+
522+ // RDへ情報送信
523+ reportProgress(MSGID+"レコーダーに" + action + "を要求します.");
524+ String [] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/folderset/DeleteFolder.php?" + pstr, null, thisEncoding);
525+// String header = d[0];
526+ String response = d[1];
527+
528+ // レスポンスから処理結果を取得する
529+ String [] rc = getReasonFromResponse( response );
530+ if (rc == null) {
531+ errmsg = ERRID+ERRMSG_NORESPONSE;
532+ return(false);
533+ }
534+
535+ String result = rc[0];
536+ String reason = rc[1];
537+
538+ if (result.equals(RESULT_OK))
539+ break;
540+
541+ if (result.equals(RESULT_OTHER) || result.equals(RESULT_BUSY)){
542+ if (mountUsbDrive(device_id))
543+ continue;
544+ }
545+
546+ errmsg = ERRID+action+"に失敗しました。(result=" + result + ",reason=" + reason + ")";
547+ return(false);
548+ }
549+
550+ // フォルダ一覧を取得し直す
551+ setSettingFolders();
552+ saveFolders();
553+
554+ return true;
555+ }
556+
557+ /*******************************************************************************
558+ * タイトル一覧取得
559+ ******************************************************************************/
560+ @Override
561+ public boolean GetRdTitles(String device_id, boolean force, boolean detail, boolean mountedOnly) {
562+ System.out.println("レコーダからタイトル一覧を取得します("+force+"): "+getRecorderId()+"("+getIPAddr()+":"+getPortNo()+")");
563+
564+ errmsg = "";
565+
566+ DEVICE_ID = device_id;
567+
568+ String myFileId = getIPAddr()+"_"+getPortNo()+"_"+getRecorderId();
569+
570+ ArrayList<TitleInfo> list = new ArrayList<TitleInfo>();
571+
572+ for (DeviceInfo di : getDeviceInfos() ) {
573+ String devid = di.getId();
574+ if (devid.equals(DEVICE_ALL))
575+ continue;
576+ if (!device_id.equals(DEVICE_ALL) && !device_id.equals(devid))
577+ continue;
578+
579+ titleFile = "env/title."+myFileId+"." + devid + ".xml";
580+
581+ if (!force){
582+ File f = new File(titleFile);
583+ if (!f.exists())
584+ return(false);
585+
586+ // キャッシュから読み出し(タイトル一覧)
587+ ArrayList<TitleInfo> ta = TitlesFromFile(titleFile);
588+
589+ list.addAll(ta);
590+ }
591+ else{
592+ // タイトル一覧をレコーダから取得する
593+ ArrayList<TitleInfo> ta = getTitleList(devid, mountedOnly);
594+ if (ta == null){
595+ if (errmsg.length() > 0)
596+ return(false);
597+
598+ File f = new File(titleFile);
599+ // キャッシュから読み出し(タイトル一覧)
600+ if (f.exists())
601+ ta = TitlesFromFile(titleFile);
602+ }
603+ else{
604+ // タイトルの詳細情報を取得し、内容を調整する
605+ for (TitleInfo entry : ta) {
606+ // 放送局名変換
607+ entry.setCh_name(getChCode().getCH_REC2WEB(entry.getChannel()));
608+ }
609+
610+ if (detail){
611+ getTitleDetails(devid, ta, false);
612+ }
613+ }
614+
615+ list.addAll(ta);
616+ }
617+ }
618+
619+ setTitles(list);
620+
621+ // キャッシュに保存
622+ if (force){
623+ saveTitles(device_id);
624+
625+ // 取得した情報の表示
626+ if (getDebug()){
627+ System.out.println("---Title List Start---");
628+ for ( int i = 0; i<getTitles().size(); i++ ) {
629+ // 詳細情報の取得
630+ TitleInfo t = getTitles().get(i);
631+ System.out.println(String.format("[%s] %s\t%s\t%s:%s\t%s:%s\t%sm\t%s\t%s\t%s",
632+ (i+1), t.getId(), t.getRec_date(), t.getAhh(), t.getAmm(), t.getZhh(), t.getZmm(), t.getRec_min(),
633+ t.getTitle(), t.getChannel(), t.getCh_name()));
634+ }
635+ System.out.println("---Title List End---");
636+ }
637+
638+ // 各種設定情報をレコーダから取得する
639+ if (setSettingDevice()){
640+ saveDeviceInfos();
641+ }
642+ }
643+
644+ return(true);
645+ }
646+
647+ /*******************************************************************************
648+ * タイトル詳細情報取得
649+ ******************************************************************************/
650+ @Override
651+ public boolean GetRdTitleDetails(String devid, boolean force){
652+ ArrayList<TitleInfo> ta = getTitles();
653+
654+ getTitleDetails(devid, ta, force);
655+
656+ // キャッシュに保存
657+ saveTitles(devid);
658+
659+ return(true);
660+ }
661+
662+ /*
663+ * タイトル詳細情報を取得する
664+ */
665+ private boolean getTitleDetails(String devid, ArrayList<TitleInfo>ta, boolean force){
666+ int tno = 0;
667+
668+ for (TitleInfo ti : ta){
669+ tno++;
670+
671+ if (ti.getDetailLoaded() && !ti.getRecording() && !force)
672+ continue;
673+
674+ reportProgress("+タイトル詳細を取得します("+tno+"/"+ta.size()+").");
675+ getTitleDetail(ti);
676+ }
677+
678+ return(true);
679+ }
680+
681+ /*******************************************************************************
682+ * タイトル詳細情報取得
683+ ******************************************************************************/
684+ @Override
685+ public boolean GetRdTitleDetail(TitleInfo t) {
686+ if (t == null)
687+ return(false);
688+
689+ if (!getTitleDetail(t))
690+ return(false);
691+
692+ // キャッシュに保存
693+ saveTitles(t.getRec_device());
694+
695+ return(true);
696+ }
697+
698+ /*******************************************************************************
699+ * タイトル更新
700+ ******************************************************************************/
701+ @Override
702+ public boolean UpdateRdTitleInfo(String device_id, TitleInfo o, TitleInfo t) {
703+ errmsg = "";
704+
705+ if (t == null) {
706+ return(false);
707+ }
708+
709+ for (int n=0; n<3; n++){
710+ // タイトルの編集をレコーダに通知する
711+ notifyTitleEdit(device_id, t.getId());
712+
713+ // おまじない
714+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
715+
716+ // RDへの情報作成
717+ String pstr = createTitlePostData(t, o, device_id);
718+
719+ // RDへ情報送信
720+ reportProgress(MSGID+"レコーダーにタイトル更新を要求します:"+device_id);
721+ String [] d = reqPOST("http://"+getIPAddr()+":"+getPortNo()+"/titlelist/UpdateTitleInfo.php", pstr, null, thisEncoding);
722+// String header = d[0];
723+ String response = d[1];
724+
725+ // レスポンスから処理結果を取得する
726+ String [] rc = getReasonFromResponse( response );
727+ if (rc == null) {
728+ errmsg = ERRID+ERRMSG_NORESPONSE;
729+ return(false);
730+ }
731+
732+ String result = rc[0];
733+ String reason = rc[1];
734+
735+ if (result.equals(RESULT_OK))
736+ break;
737+
738+ if (result.equals(RESULT_OTHER)){
739+ if (mountUsbDrive(device_id))
740+ continue;
741+ }
742+
743+ errmsg = ERRID+"タイトル更新に失敗しました。(result=" + result + ",reason=" + reason + ")";
744+ return(false);
745+ }
746+
747+ // 録画タイトルリストを更新
748+ ArrayList<TitleInfo> list = getTitles();
749+ list.remove(o);
750+ list.add(t);
751+ list.sort(new TitleInfoComparator());
752+
753+ // キャッシュに保存
754+ saveTitles(t.getRec_device());
755+
756+ // 各種設定情報をレコーダから取得する
757+ if (setSettingDevice()){
758+ saveDeviceInfos();
759+ }
760+
761+ reportProgress(MSGID+"正常に更新できました。");
762+ System.out.printf("\n<<< Message from RD >>> \"%s\"\n\n", "正常に更新できました。");
763+
764+ return(true);
765+ }
766+
767+ /*******************************************************************************
768+ * タイトル削除
769+ ******************************************************************************/
770+ @Override
771+ public TitleInfo RemoveRdTitle(String device_id, String title_id) {
772+ errmsg = "";
773+
774+ // 削除対象を探す
775+ TitleInfo t = null;
776+ for ( TitleInfo ttl : getTitles() ) {
777+ if (ttl.getId().equals(title_id)) {
778+ t = ttl;
779+ break;
780+ }
781+ }
782+ if (t == null) {
783+ return(null);
784+ }
785+
786+ for (int n=0; n<3; n++){
787+ // おまじない
788+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
789+
790+ // RDへの情報作成
791+ String pstr = "drive_id=" + device_id + "&title_id=" + title_id;
792+
793+ // RDへ情報送信
794+ reportProgress(MSGID+"レコーダーにタイトル削除を要求します.");
795+ String [] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/titlelist/DeleteTitle.php?"+pstr, null, thisEncoding);
796+// String header = d[0];
797+ String response = d[1];
798+
799+ // レスポンスから処理結果を取得する
800+ String [] rc = getReasonFromResponse( response );
801+ if (rc == null) {
802+ errmsg = ERRID+ERRMSG_NORESPONSE;
803+ return(null);
804+ }
805+
806+ String result = rc[0];
807+ String reason = rc[1];
808+
809+ if (result.equals(RESULT_OK))
810+ break;
811+
812+ if (result.equals(RESULT_OTHER) || result.equals(RESULT_INVALID_TITLE)){
813+ if (mountUsbDrive(device_id))
814+ continue;
815+ }
816+
817+ errmsg = ERRID+"タイトル削除に失敗しました。(result=" + result + ",reason=" + reason + ")";
818+ return(null);
819+ }
820+
821+ // タイトルリストを更新
822+ getTitles().remove(t);
823+
824+ // キャッシュに保存
825+ saveTitles(t.getRec_device());
826+
827+ setSettingDevice();
828+ saveDeviceInfos();
829+
830+ reportProgress(MSGID+"正常に削除できました。");
831+ System.out.printf("\n<<< Message from RD >>> \"%s\"\n\n", "正常に削除できました。");
832+ return(t);
833+ }
834+
835+ /*******************************************************************************
836+ * タイトル再生の開始・終了
837+ ******************************************************************************/
838+ @Override
839+ public boolean StartStopPlayRdTitle(String device_id, String title_id, boolean start) {
840+ errmsg = "";
841+
842+ // RDへのURL
843+ String action = start ? "タイトル再生開始" : "タイトル再生終了";
844+ String url = start ? "/titlelist/PlayTitle.php" : "/titlelist/PlayStop.php";
845+
846+ // RDへの情報作成
847+ String pstr = "drive_id=" + device_id + "&title_id=" + title_id;
848+
849+ for (int n=0; n<3; n++){
850+ // おまじない
851+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
852+
853+ // RDへ情報送信
854+ reportProgress(MSGID+"レコーダーに" + action + "を要求します.");
855+ String [] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+url+ "?"+pstr, null, thisEncoding);
856+// String header = d[0];
857+ String response = d[1];
858+
859+ // レスポンスから処理結果を取得する
860+ String [] rc = getReasonFromResponse( response );
861+ if (rc == null) {
862+ errmsg = ERRID+ERRMSG_NORESPONSE;
863+ return(false);
864+ }
865+
866+ String result = rc[0];
867+ String reason = rc[1];
868+
869+ if (result.equals(RESULT_OK))
870+ break;
871+
872+ if (result.equals(RESULT_OTHER)){
873+ if (mountUsbDrive(device_id))
874+ continue;
875+ }
876+
877+ errmsg = ERRID+action+"に失敗しました。(result=" + result + ",reason=" + reason + ")";
878+ return(false);
879+ }
880+
881+ reportProgress(MSGID+"正常に" + action + "できました。");
882+
883+ return(true);
884+ }
885+
886+ /* ここまで */
887+
888+ /* 個別コード-ここから最後まで */
889+ /*******************************************************************************
890+ * 非公開メソッド
891+ ******************************************************************************/
892+ /*
893+ * 録画予約の一覧を取得する
894+ */
895+ protected ArrayList<ReserveList> getReserveList() {
896+ // おまじない
897+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
898+
899+ // RDから予約一覧を取り出す
900+ reportProgress(MSGID+"予約一覧を取得します.");
901+ String response = "";
902+ if (!test){
903+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/LoadReserveList.php",null, thisEncoding);
904+// String header = d[0];
905+ response= d[1];
906+ }
907+ else{
908+ response= "{\"NETdeNAVI\":{\"torunaviInfo\":[{\"num\":1,\"id\":747,\"exec\":1,\"option\":18,\"eventname\":\"[生]2019 F1グランプリ 最終戦 アブダビGP 土曜フリー走行\",\"network\":2,\"ch\":\"309\",\"repeat\":0,\"datetime\":1575107400,\"duration\":4800,\"conflictstart\":0,\"conflictend\":0,\"recording\":0,\"last\":0,\"mochidashi\":1},{\"num\":2,\"id\":748,\"exec\":1,\"option\":18,\"eventname\":\"[生]2019 F1グランプリ 最終戦 アブダビGP 予選\",\"network\":2,\"ch\":\"309\",\"repeat\":0,\"datetime\":1575118200,\"duration\":7800,\"conflictstart\":0,\"conflictend\":0,\"recording\":0,\"last\":0,\"mochidashi\":1},{\"num\":3,\"id\":754,\"exec\":1,\"option\":2,\"eventname\":\"2019 F1グランプリ 最終戦 アブダビGP 決勝\",\"network\":2,\"ch\":\"309\",\"repeat\":0,\"datetime\":1575203400,\"duration\":12600,\"conflictstart\":0,\"conflictend\":0,\"recording\":0,\"last\":0,\"mochidashi\":0}],\"time\":1575102247}}";
909+ }
910+
911+ if (response == null) {
912+ errmsg = ERRID+ERRMSG_NORESPONSE;
913+ return(null);
914+ }
915+
916+ // 先頭部分をチェックする
917+ Matcher mr = Pattern.compile("\\{\"NETdeNAVI\"").matcher(response);
918+ if ( ! mr.find()) {
919+ errmsg = ERRID+ERRMSG_INVALIDRESPONSE;
920+ return (null);
921+ }
922+
923+ ArrayList<ReserveList> list = new ArrayList<ReserveList>();
924+
925+ Matcher ma = Pattern.compile("\\{" +
926+ "\"num\":(\\d+)," + // 1
927+ "\"id\":(\\d+)," + // 2
928+ "\"exec\":(\\d+)," + // 3
929+ "\"option\":(\\d+)," + // 4
930+ "\"eventname\":\"([^\"]+)\"," + // 5
931+ "\"network\":(\\d+)," + // 6
932+ "\"ch\":\"([^\"]+)\"," + // 7
933+ "\"repeat\":(\\d+)," + // 8
934+ "\"datetime\":(\\d+)," + // 9
935+ "\"duration\":(\\d+)," + // 10
936+ "\"conflictstart\":(\\d+)," + // 11
937+ "\"conflictend\":(\\d+)," + // 12
938+ "\"recording\":(\\d+)," + // 13
939+ "\"last\":(\\d+)," + // 14
940+ "\"mochidashi\":(\\d+)\\}") // 15
941+ .matcher(response);
942+
943+ while ( ma.find() ) {
944+ // 個々のデータを取り出す
945+ ReserveList entry = new ReserveList();
946+
947+ String id = ma.group(2);
948+ String exec = ma.group(3);
949+ String option = ma.group(4);
950+ String eventname = ma.group(5);
951+ String network = ma.group(6);
952+ String ch = ma.group(7);
953+ String repeat = ma.group(8);
954+ String datetime = ma.group(9);
955+ String duration = ma.group(10);
956+ String recording = ma.group(13);
957+ String mochidashi = ma.group(15);
958+
959+ // 予約ID
960+ entry.setId(id);
961+
962+ // 基本情報をセットする
963+ setReserveBasicInfo(entry, exec, option, eventname, network, ch, repeat, datetime, duration);
964+
965+ // 持ち出し
966+ String portable_name = value2text(portable, mochidashi);
967+ entry.setRec_portable(portable_name);
968+
969+ // 予約情報を保存
970+ list.add(entry.clone());
971+ }
972+
973+ return(list);
974+ }
975+
976+ /***
977+ * 予約の基本情報をセットする
978+ */
979+ protected void setReserveBasicInfo(ReserveList entry, String exec, String option, String eventname,
980+ String network, String ch, String repeat, String datetime, String duration){
981+ // 実行ON/OFF
982+ entry.setExec(exec.equals(EXEC_YES));
983+
984+ // オプション(1=日付指定, 2=PGM指定)
985+ entry.setRec_option(option);
986+
987+ // 開始日、終了日
988+ int nbsecs = Integer.parseInt(datetime); // 開始日時(UNIX時間/1000)
989+ int secs = Integer.parseInt(duration); // 録画時間(sec)
990+ int nesecs = nbsecs + secs; // 終了日時(UNIX時間/1000)
991+ Date bdate = new Date(nbsecs*1000L); // 開始日時(Date)
992+ Date edate = new Date(nesecs*1000L); // 終了日時(Date)
993+
994+ SimpleDateFormat sfd = new SimpleDateFormat("yyyy/MM/dd(E)", Locale.JAPAN);
995+ String pattern = sfd.format(bdate);
996+
997+ // 繰り返しパターン
998+ String pid = String.valueOf(RPTPTN_ID_BYDATE);
999+
1000+ if (!repeat.equals(REPEAT_NONE)){
1001+ pattern = value2text(tvsPatternName, repeat);
1002+ pid = value2text(tvsPatternCode, repeat);
1003+ }
1004+
1005+ entry.setRec_pattern_id(Integer.parseInt(pid));
1006+ entry.setRec_pattern(pattern);
1007+
1008+ // 開始、終了時刻
1009+ SimpleDateFormat sfh = new SimpleDateFormat("HH");
1010+ SimpleDateFormat sfm = new SimpleDateFormat("mm");
1011+ String ahh = sfh.format(bdate);
1012+ String amm = sfm.format(bdate);
1013+ String zhh = sfh.format(edate);
1014+ String zmm = sfm.format(edate);
1015+ entry.setAhh(ahh);
1016+ entry.setAmm(amm);
1017+ entry.setZhh(zhh);
1018+ entry.setZmm(zmm);
1019+
1020+ // 次の録画日などを計算する
1021+ entry.setRec_nextdate(CommonUtils.getNextDate(entry));
1022+ entry.setRec_min(String.valueOf(secs/60));
1023+ getStartEndDateTime(entry);
1024+
1025+ // チューナーは固定
1026+ entry.setTuner("R1");
1027+
1028+ // 録画モードもとりあえず固定(後で詳細情報で上書きする)
1029+ entry.setRec_mode(RECMODE_NAME_DR);
1030+
1031+ // タイトル
1032+ String title = unescapeJavaString(eventname);
1033+ entry.setTitle(title);
1034+ entry.setTitlePop(TraceProgram.replacePop(title));
1035+
1036+ // チャンネル
1037+ switch(network){
1038+ case NETWORK_UVD:
1039+ break;
1040+ case NETWORK_BSD:
1041+ ch = CHPREFIX_BS + ch;
1042+ break;
1043+ case NETWORK_CSD:
1044+ ch = CHPREFIX_CS + ch;
1045+ break;
1046+ case NETWORK_L1:
1047+ ch = CH_L1;
1048+ break;
1049+ case NETWORK_L2:
1050+ ch = CH_L2;
1051+ break;
1052+ case NETWORK_L3:
1053+ ch = CH_L3;
1054+ break;
1055+ case NETWORK_L4:
1056+ ch = CH_L4;
1057+ break;
1058+ case NETWORK_NET:
1059+ ch = CH_NET;
1060+ break;
1061+ }
1062+
1063+ entry.setChannel(ch);
1064+
1065+ // 放送局名変換
1066+ entry.setCh_name(getChCode().getCH_REC2WEB(entry.getChannel()));
1067+ }
1068+
1069+ /*
1070+ * 予約詳細情報を取得する
1071+ */
1072+ protected boolean getReserveDetail( ReserveList r) {
1073+ // おまじない
1074+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
1075+
1076+ String id = r.getId();
1077+ String response = "";
1078+
1079+ if (!test){
1080+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/LoadReserveDetailData.php?reserve_id=" + id, null, thisEncoding);
1081+// String header = d[0];
1082+ response= d[1];
1083+ }
1084+ else{
1085+ response = "{\"NETdeNAVI\":{\"reserveInfo\":{\"id\":748,\"exec\":1,\"option\":18,\"eventname\":\"[生]2019 F1グランプリ 最終戦 アブダビGP 予選\",\"network\":2,\"chnum\":309,\"branchexists\":false,\"branchnum\":0,\"ch\":\"309\",\"repeat\":0,\"datetime\":1575118200,\"duration\":6000,\"conflictstart\":0,\"conflictend\":0,\"drive_id\":0,\"folder_id\":0,\"recmode\":65,\"mochidashi\":1,\"video_es\":0,\"audio_es\":0,\"data_es\":0,\"recording\":1}}}";
1086+ }
1087+
1088+ if (response == null) {
1089+ errmsg = ERRID+ERRMSG_NORESPONSE;
1090+ System.out.println(errmsg);
1091+ return(false);
1092+ }
1093+
1094+ // {"NETdeNAVI":{"reserveInfo":{"id":103,"exec":1,"option":2,"eventname":"Re:CREATORS(レクリエイターズ)",
1095+ // "network":0,"chnum":91,"branchexists":false,"branchnum":0,"ch":"091","repeat":64,"datetime":1495290600,"duration":1800,
1096+ // "conflictstart":0,"conflictend":0,"drive_id":512,"folder_id":33,"recmode":0,"mochidashi":0,"video_es":0,"audio_es":0,
1097+ // "data_es":0,"recording":0}}}
1098+ Matcher ma = Pattern.compile("\\{" +
1099+ "\"id\":(\\d+)," + // 1
1100+ "\"exec\":(\\d+)," + // 2
1101+ "\"option\":(\\d+)," + // 3
1102+ "\"eventname\":\"([^\"]+)\"," + // 4
1103+ "\"network\":(\\d+)," + // 5
1104+ "\"chnum\":(\\d+)," + // 6
1105+ "\"branchexists\":(false|true)," + // 7
1106+ "\"branchnum\":(\\d+)," + // 8
1107+ "\"ch\":\"([^\"]+)\"," + // 9
1108+ "\"repeat\":(\\d+)," + // 10
1109+ "\"datetime\":(\\d+)," + // 11
1110+ "\"duration\":(\\d+)," + // 12
1111+ "\"conflictstart\":(\\d+)," + // 13
1112+ "\"conflictend\":(\\d+)," + // 14
1113+ "\"drive_id\":(\\d+)," + // 15
1114+ "\"folder_id\":(\\d+)," + // 16
1115+ "\"recmode\":(\\d+)," + // 17
1116+ "\"mochidashi\":(\\d+)," + // 18
1117+ "\"video_es\":(\\d+)," + // 19
1118+ "\"audio_es\":(\\d+)," + // 20
1119+ "\"data_es\":(\\d+)," + // 21
1120+ "\"recording\":(\\d+)\\}") // 22
1121+ .matcher(response);
1122+
1123+ if ( !ma.find() ) {
1124+ reportProgress(ERRID+"予約詳細情報が取得できません.ID=" + id);
1125+ return (false);
1126+ }
1127+
1128+ String exec = ma.group(2);
1129+ String option = ma.group(3);
1130+ String eventname = ma.group(4);
1131+ String network = ma.group(5);
1132+ // chnum
1133+ // branchexists
1134+ // branchnum
1135+ String ch = ma.group(9);
1136+ String repeat = ma.group(10);
1137+ String datetime = ma.group(11);
1138+ String duration = ma.group(12);
1139+ // conflictstart
1140+ // conflictend
1141+ String device_id = ma.group(15);
1142+ String folder_id = ma.group(16);
1143+ String recmode = ma.group(17);
1144+ String mochidashi = ma.group(18);
1145+ // video_es
1146+ // audio_es
1147+ // data_es
1148+ // recording
1149+
1150+ // 基本情報をセットする
1151+ String tuner = r.getTuner();
1152+ setReserveBasicInfo(r, exec, option, eventname, network, ch, repeat, datetime, duration);
1153+ // チューナはそのまま
1154+ r.setTuner(tuner);
1155+
1156+ // 保存先ドライブ
1157+ String device_name = value2text(device, device_id);
1158+ r.setRec_device(device_name);
1159+
1160+ // 保存先フォルダー
1161+ String folder_name = value2text(folder, device_id + ":" + folder_id);
1162+ r.setRec_folder(folder_name);
1163+
1164+ // 録画モード
1165+ String recmode_name = value2text(vrate, recmode);
1166+ r.setRec_mode(recmode_name);
1167+
1168+ // 持ち出し
1169+ String portable_name = value2text(portable, mochidashi);
1170+ r.setRec_portable(portable_name);
1171+
1172+ return(true);
1173+ }
1174+
1175+ /**
1176+ * レコーダーから取得できない情報は直接コピー(既存のリストから探して)
1177+ */
1178+ protected void copyAttributesT2007(ReserveList to, ArrayList<ReserveList> fromlist) {
1179+ ReserveList olde = null;
1180+ for ( ReserveList from : fromlist ) {
1181+ if ( from.getId() != null && from.getId().equals(to.getId()) ) {
1182+ copyAttributeT2007(to, olde = from);
1183+ break;
1184+ }
1185+ }
1186+
1187+ // DIGAの終了時間"未定"対応だけど、別にDIGAかどうか確認したりはしない。
1188+ setAttributesDiga(to,olde);
1189+ }
1190+
1191+ /**
1192+ * レコーダーから取得できない情報は直接コピー(既存エントリから直に)
1193+ */
1194+ protected void copyAttributeT2007(ReserveList to, ReserveList from) {
1195+ // 鯛ナビの内部フラグ
1196+ to.setAutocomplete(from.getAutocomplete());
1197+
1198+ // 予約一覧からは取得できない情報
1199+ to.setDetail(from.getDetail());
1200+ to.setRec_genre(from.getRec_genre());
1201+ to.setRec_autodel(from.getRec_autodel());
1202+
1203+ // BZ700以降の取得一覧から取得できない画質の対応
1204+ if (to.getRec_mode().equals("")) {
1205+ to.setRec_mode(from.getRec_mode());
1206+ }
1207+ }
1208+
1209+ //
1210+ protected void translateAttributeTuner(ReserveList entry) {
1211+ if (entry.getTuner().startsWith("TS")) {
1212+ entry.setTuner(entry.getTuner().replaceFirst("^TS", "DR"));
1213+ }
1214+ }
1215+
1216+ /*
1217+ * 登録/削除要求のPOSTデータを生成する
1218+ */
1219+ protected String createPostData(ReserveList r, String idBefore) {
1220+
1221+ String id = idBefore;
1222+ String exec = EXEC_YES;
1223+ String option = OPTION_PROGRAM;
1224+ String eventname = "";
1225+ String network = NETWORK_UVD;
1226+ String chnum = "0";
1227+ String branchexists = BRANCH_NO;
1228+ String branchnum = "0";
1229+ String ch = "";
1230+ String repeat = REPEAT_NONE;
1231+ String datetime = "";
1232+ String duration = "";
1233+ String conflictstart = "0";
1234+ String conflictend = "0";
1235+ String drive_id = DEVICE_HDD;
1236+ String folder_id = FOLDER_NONE;
1237+ String recmode = RECMODE_DR;
1238+ String mochidashi = MOCHIDASHI_NONE;
1239+ String video_es = "0";
1240+ String audio_es = "0";
1241+ String data_es = "0";
1242+ String recording = "0";
1243+
1244+ boolean bUpdate = !idBefore.equals("");
1245+
1246+ // スキップ
1247+ if (!r.getExec())
1248+ exec = EXEC_NO;
1249+
1250+ // オプション
1251+ if (bUpdate)
1252+ option = r.getRec_option();
1253+
1254+ // タイトル(eventname)
1255+ try {
1256+ if (!r.getAutocomplete())
1257+ eventname = URLEncoder.encode(CommonUtils.substringrb(r.getTitle(),80), thisEncoding);
1258+ } catch (UnsupportedEncodingException e) {
1259+ // TODO Auto-generated catch block
1260+ e.printStackTrace();
1261+ }
1262+
1263+ // チャンネル(ch)
1264+ String channel = cc.getCH_WEB2CODE(r.getCh_name());
1265+ ch = cc.getCH_CODE2REC(channel);
1266+
1267+ // 枝番有(branchexists,branchnum)
1268+ branchnum = text2value(tvsBranch, channel);
1269+ branchexists = Integer.parseInt(branchnum) > 0 ? BRANCH_YES : BRANCH_NO;
1270+
1271+ // ネットワーク(network)
1272+ String typ = text2value(chtype, channel);
1273+ switch(typ){
1274+ case CHTYPE_UVD:
1275+ network = NETWORK_UVD; // 地上デジタル
1276+ break;
1277+ case CHTYPE_BSD:
1278+ network = NETWORK_BSD; // BSデジタル
1279+ ch = ch.substring(CHPREFIX_BS.length());
1280+ break;
1281+ case CHTYPE_CSD:
1282+ network = NETWORK_CSD; // 110度CSデジタル
1283+ ch = ch.substring(CHPREFIX_CS.length());
1284+ break;
1285+ case CHTYPE_L1:
1286+ network = NETWORK_L1; // 外部入力(L1)
1287+ break;
1288+ case CHTYPE_L2:
1289+ network = NETWORK_L2; // 外部入力(L2)
1290+ break;
1291+ case CHTYPE_L3:
1292+ network = NETWORK_L3; // 外部入力(L3)
1293+ break;
1294+ case CHTYPE_L4:
1295+ network = NETWORK_L4; // 外部入力(L4)
1296+ break;
1297+ case CHTYPE_NET:
1298+ network = NETWORK_NET; // 外部入力(NET)
1299+ break;
1300+ default:
1301+ // 普通ここには落ちない
1302+ if (ch.startsWith("C")) {
1303+ network = NETWORK_L1; // "C***"は外部入力(L1)
1304+ }
1305+ else if (ch.startsWith("SP")) {
1306+ network = NETWORK_L3; // "SP***"は外部入力(L3)
1307+ }
1308+ else {
1309+ network = NETWORK_L2; // 未定義は全部外部入力(L2)
1310+ }
1311+ break;
1312+ }
1313+
1314+ chnum = ch;
1315+
1316+ // 繰り返しパターン(repeat)
1317+ String pattern = r.getRec_pattern();
1318+ repeat = text2value(tvsPatternName, pattern);
1319+ if (repeat.equals(""))
1320+ repeat = REPEAT_NONE; // 繰り返しなし
1321+
1322+ // 開始日時(datetime)
1323+ if (!repeat.equals(REPEAT_NONE)){
1324+ String pid = value2text(tvsPatternCode, repeat);
1325+ r.setRec_pattern_id(Integer.parseInt(pid));
1326+
1327+ pattern = CommonUtils.getNextDate(r);
1328+ }
1329+
1330+ Matcher ma = Pattern.compile("^(\\d+)/(\\d+)/(\\d+)").matcher(pattern);
1331+ if (ma.find()){
1332+ String startDateTime = String.format("%04d%02d%02d %02d:%02d:00",
1333+ Integer.parseInt(ma.group(1)),
1334+ Integer.parseInt(ma.group(2)),
1335+ Integer.parseInt(ma.group(3)),
1336+ Integer.parseInt(r.getAhh()),
1337+ Integer.parseInt(r.getAmm()));
1338+
1339+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
1340+
1341+ // Date型に変換する
1342+ Date date = new Date();
1343+ try {
1344+ date = sdf.parse(startDateTime);
1345+ } catch (ParseException e) {
1346+ // TODO 自動生成された catch ブロック
1347+ e.printStackTrace();
1348+ }
1349+
1350+ // UNIX時刻/1000を取得する(単位秒)
1351+ datetime = String.valueOf(date.getTime()/1000);
1352+ }
1353+
1354+ // 録画時間(duration)(単位秒)
1355+ String min = CommonUtils.getRecMin(r.getAhh(),r.getAmm(),r.getZhh(),r.getZmm());
1356+ duration = String.valueOf(Integer.parseInt(min)*60);
1357+
1358+ // ドライブ(drive_id)/フォルダ(folder_id)
1359+ drive_id = text2value(device, r.getRec_device());
1360+ String s = text2value(folder, r.getRec_folder());
1361+
1362+ // <drive_id>:<folder_id> から<folder_id>を取り出す。ただし「指定なし」は"0"
1363+ int idx = s.indexOf(':');
1364+ if (idx != -1)
1365+ folder_id = s.substring(idx+1);
1366+
1367+ // 録画モード(recmode)
1368+ recmode = text2value(vrate, r.getRec_mode());
1369+
1370+ // 持ち出し
1371+ mochidashi = text2value(portable, r.getRec_portable());
1372+
1373+ String postData =
1374+ "reserveInfo[id]=" + id + "&" +
1375+ "reserveInfo[exec]=" + exec + "&" +
1376+ "reserveInfo[option]=" + option + "&" +
1377+ "reserveInfo[eventname]=" + eventname + "&" +
1378+ "reserveInfo[network]=" + network + "&" +
1379+ "reserveInfo[chnum]=" + chnum + "&" +
1380+ "reserveInfo[branchexists]=" + branchexists + "&" +
1381+ "reserveInfo[branchnum]=" + branchnum + "&" +
1382+ "reserveInfo[ch]=" + ch + "&" +
1383+ "reserveInfo[repeat]=" + repeat + "&" +
1384+ "reserveInfo[datetime]=" + datetime + "&" +
1385+ "reserveInfo[duration]=" + duration + "&" +
1386+ "reserveInfo[conflictstart]=" + conflictstart + "&" +
1387+ "reserveInfo[conflictend]=" + conflictend + "&" +
1388+ "reserveInfo[drive_id]=" + drive_id + "&" +
1389+ "reserveInfo[folder_id]=" + folder_id + "&" +
1390+ "reserveInfo[recmode]=" + recmode + "&" +
1391+ "reserveInfo[mochidashi]=" + mochidashi + "&" +
1392+ "reserveInfo[video_es]=" + video_es + "&" +
1393+ "reserveInfo[audio_es]=" + audio_es + "&" +
1394+ "reserveInfo[data_es]=" + data_es + "&" +
1395+ "reserveInfo[recording]=" + recording;
1396+
1397+ if (getDebug())
1398+ System.out.println("PostData=[" + postData + "]");
1399+
1400+ return postData;
1401+ }
1402+
1403+ /*
1404+ * 予約登録画面の初期化情報を取得する。これを呼ばないと、後で登録要求した時にERR_EXCLUSIVEエラーになる
1405+ */
1406+ protected boolean loadDialogInitData( String id) {
1407+ // おまじない
1408+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
1409+
1410+ reportProgress(MSGID+"予約登録画面を初期化します.");
1411+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/LoadDialogInitializationData.php?schId=0", null, thisEncoding);
1412+// String header = d[0];
1413+ String response= d[1];
1414+
1415+ if (response == null) {
1416+ errmsg = ERRID+ERRMSG_NORESPONSE;
1417+ System.out.println(errmsg);
1418+ return(false);
1419+ }
1420+
1421+ return(true);
1422+ }
1423+
1424+ /*
1425+ * 登録要求の応答をチェックする
1426+ */
1427+ protected boolean checkReserveResponse( ReserveList r, String response ) {
1428+ if (response == null) {
1429+ errmsg = ERRID+ERRMSG_NORESPONSE;
1430+ return(false);
1431+ }
1432+
1433+ // {"result":24,"reason":"ERR_EXCLUSIVE","schid":"0"}
1434+ Matcher ma = Pattern.compile("\\{" +
1435+ "\"result\":(\\d+)," +
1436+ "\"reason\":\"([^\"]*)\"," +
1437+ "\"schid\":\"(\\d+)\"\\}").matcher(response);
1438+
1439+ if ( ma.find() ) {
1440+ String result = ma.group(1);
1441+ String reason = ma.group(2);
1442+ String schid = ma.group(3);
1443+
1444+ switch(result){
1445+ case RESULT_OK:
1446+ break;
1447+ case "3":
1448+ reportProgress("3:番組が見つからなかったため、日時指定予約として登録しました。");
1449+ break;
1450+ case "4":
1451+ reportProgress("4:予約が重複しています。");
1452+ break;
1453+ case "5":
1454+ reportProgress("5:持ち出し変換の上限を超えているため、持ち出し設定を「なし」として登録しました。");
1455+ break;
1456+ case "6":
1457+ reportProgress("6:持ち出し変換の上限を超えているため、持ち出し設定を「なし」として登録しました。");
1458+ break;
1459+ case "7":
1460+ reportProgress("7:番組が見つからなかったため、日時指定予約として登録しました。");
1461+ break;
1462+ case "8":
1463+ reportProgress("8:番組が見つからなかったため、日時指定予約として登録しました。\n" +
1464+ "持ち出し変換の上限を超えているため、持ち出し設定を「なし」として登録しました。");
1465+ break;
1466+ case "9":
1467+ reportProgress("9:番組が見つからなかったため、日時指定予約として登録しました。\n" +
1468+ "持ち出し変換の上限を超えているため、持ち出し設定を「なし」として登録しました。");
1469+ break;
1470+ default:
1471+ errmsg = ERRID+"予約登録に失敗しました。(result=" + result + ",reason=" + reason + ")";
1472+ reportProgress(errmsg);
1473+ return(false);
1474+ }
1475+
1476+ if (! schid.equals("0")){
1477+ r.setId(schid);
1478+ reportProgress(MSGID+"予約IDは"+r.getId()+"です。");
1479+ }
1480+
1481+ if (!result.equals(RESULT_OK))
1482+ getReserveDetail(r);
1483+ }
1484+ else{
1485+ errmsg = ERRID+ERRMSG_INVALIDRESPONSE;
1486+ reportProgress(errmsg);
1487+ return(false);
1488+ }
1489+
1490+ return(true);
1491+ }
1492+
1493+ /*
1494+ * 予約内容を調整する
1495+ */
1496+ protected void adjustReserve(ReserveList r) {
1497+ // 予約パターンID
1498+ r.setRec_pattern_id(getRec_pattern_Id(r.getRec_pattern()));
1499+
1500+ // 次回予定日
1501+ r.setRec_nextdate(CommonUtils.getNextDate(r));
1502+
1503+ // 録画長
1504+ r.setRec_min(CommonUtils.getRecMin(r.getAhh(),r.getAmm(),r.getZhh(),r.getZmm()));
1505+
1506+ // 開始日時・終了日時
1507+ getStartEndDateTime(r);
1508+ }
1509+
1510+ /*
1511+ * フォルダの作成ないしフォルダ名称を変更する
1512+ */
1513+ protected boolean setFolderName(String device_id, String fol_id, String folder_name) {
1514+ String action = fol_id != null ? "フォルダ名更新" : "フォルダ作成";
1515+ String folder_id = extractFolderID(fol_id);
1516+
1517+ String fnameEnc = "";
1518+ try {
1519+ fnameEnc = URLEncoder.encode(CommonUtils.substringrb(folder_name,80), thisEncoding);
1520+ } catch (UnsupportedEncodingException e) {
1521+ // TODO 自動生成された catch ブロック
1522+ e.printStackTrace();
1523+ }
1524+
1525+ String pstr =
1526+ "drive_id=" + device_id + "&" +
1527+ "folder_id=" + (folder_id != null ? folder_id : "NEWFOLDER") + "&" +
1528+ "folder_name=" + fnameEnc;
1529+
1530+ for (int n=0; n<3; n++){
1531+ // おまじない
1532+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
1533+
1534+ // RDへ情報送信
1535+ reportProgress(MSGID+"レコーダーに" + action + "を要求します.");
1536+ String [] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/folderset/SetFolderName.php?" + pstr, null, thisEncoding);
1537+// String header = d[0];
1538+ String response = d[1];
1539+
1540+ // レスポンスから処理結果を取得する
1541+ String [] rc = getReasonFromResponse( response );
1542+ if (rc == null) {
1543+ errmsg = ERRID+ERRMSG_NORESPONSE;
1544+ return(false);
1545+ }
1546+
1547+ String result = rc[0];
1548+ String reason = rc[1];
1549+
1550+ if (result.equals(RESULT_OK))
1551+ break;
1552+
1553+ if (result.equals(RESULT_OTHER) || result.equals(RESULT_BUSY)){
1554+ if (mountUsbDrive(device_id))
1555+ continue;
1556+ }
1557+
1558+ errmsg = ERRID+action+"に失敗しました。(result=" + result + ",reason=" + reason + ")";
1559+ return(false);
1560+ }
1561+
1562+ // フォルダ一覧を取得し直す
1563+ setSettingFolders();
1564+ saveFolders();
1565+
1566+ // タイトルに含まれるフォルダ名を更新する
1567+ updateFolderNameOfTitles();
1568+
1569+ return (true);
1570+ }
1571+
1572+ /*
1573+ * タイトルに含まれるフォルダ名を更新する
1574+ */
1575+ protected void updateFolderNameOfTitles(){
1576+ ArrayList<TitleInfo> list = getTitles();
1577+
1578+ for (TitleInfo ti : list){
1579+ ArrayList<TextValueSet> ts = ti.getRec_folder();
1580+
1581+ for (TextValueSet t : ts){
1582+ t.setText(value2text(folder, t.getValue()));
1583+ }
1584+
1585+ ti.setRec_folder(ts);
1586+ }
1587+
1588+ setTitles(list);
1589+
1590+ saveTitles(DEVICE_ID);
1591+ }
1592+ /*
1593+ * USB-HDDをマウントする
1594+ */
1595+ protected boolean mountUsbDrive(String device_id) {
1596+ reportProgress(MSGID+"ドライブをマウントします:"+device_id);
1597+
1598+ String pstr = "drive_id=" + device_id;
1599+
1600+ // おまじない
1601+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
1602+
1603+ // RDへ情報送信
1604+ String [] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/titlelist/ChangeMountedUSB.php?" + pstr, null, thisEncoding);
1605+// String header = d[0];
1606+ String response = d[1];
1607+
1608+ // 結果の確認
1609+ if ( !checkGeneralResponse( "USB HDDのマウント", response) ){
1610+ return(false);
1611+ }
1612+
1613+ return true;
1614+ }
1615+
1616+ /*
1617+ * 一般的な応答をチェックする
1618+ */
1619+ protected boolean checkGeneralResponse( String action, String response){
1620+ String [] rc = getReasonFromResponse( response );
1621+
1622+ if (rc == null) {
1623+ errmsg = ERRID+ERRMSG_NORESPONSE;
1624+ return(false);
1625+ }
1626+
1627+ String result = rc[0];
1628+ String reason = rc[1];
1629+
1630+ if (! result.equals(RESULT_OK)){
1631+ errmsg = action + "に失敗しました。(result=" + result + ",reason=" + reason + ")";
1632+ reportProgress(errmsg);
1633+ return(false);
1634+ }
1635+
1636+ return(true);
1637+ }
1638+
1639+ /*
1640+ * 応答メッセージから結果を取得する
1641+ */
1642+ protected String[] getReasonFromResponse( String response){
1643+ if (response == null) {
1644+ return(null);
1645+ }
1646+
1647+ // 先頭部分をチェックする
1648+ Matcher mh = Pattern.compile("\\{\"NETdeNAVI\"").matcher(response);
1649+ if ( ! mh.find()) {
1650+ return (null);
1651+ }
1652+
1653+ // 応答メッセージをパースする
1654+ // {"result":24,"reason":"ERR_EXCLUSIVE"}
1655+ Matcher ma = Pattern.compile("\\{" +
1656+ "\"result\":(\\d+)," +
1657+ "\"reason\":\"([^\"]*)\"\\}").matcher(response);
1658+ if ( !ma.find() )
1659+ return(null);
1660+
1661+ String [] rc = new String[2];
1662+
1663+ rc[0] = ma.group(1);
1664+ rc[1] = ma.group(2);
1665+
1666+ return(rc);
1667+ }
1668+
1669+ /*
1670+ * TitleInfoを startDateTime, cotent_id順にソートする
1671+ *
1672+ */
1673+ public class TitleInfoComparator implements Comparator<TitleInfo>{
1674+
1675+ @Override
1676+ public int compare(TitleInfo p1, TitleInfo p2) {
1677+ int rc = p1.getStartDateTime().compareTo(p2.getStartDateTime());
1678+ if (rc != 0){
1679+ return rc;
1680+ }
1681+
1682+ return p1.getSerial() - p2.getSerial();
1683+ }
1684+ }
1685+ /*
1686+ * タイトル一覧を取得する
1687+ */
1688+ protected ArrayList<TitleInfo> getTitleList(String device_id, boolean mountedOnly) {
1689+ String str = "drive=" + device_id + "&org_pl=0";
1690+ String response = "";
1691+
1692+ for (int n=0; n<3; n++){
1693+ // おまじない
1694+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
1695+
1696+ // RDからタイトル一覧を取り出す
1697+ reportProgress(MSGID+"タイトル一覧を取得します:"+device_id);
1698+ if (!test){
1699+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/titlelist/LoadTitleList.php?" + str, null, thisEncoding);
1700+// String header = d[0];
1701+ response= d[1];
1702+ }
1703+ else{
1704+ response = "{\"NETdeNAVI\":{\"folderInfo\":[{\"folder_id\":0,\"foldername\":\"すべて\"}],\"driveInfo\":{\"current_rectype\":\"AN\",\"remain_str\":\"110時間37分\",\"direct_rec\":true,\"folder_enable\":true,\"playlist_enable\":true},\"is_rating_pass_enabled\":true,\"titleListInfo\":[{\"num\":1,\"id\":\"5daa27d0000a\",\"titlename\":\"【最新作】[映][SS]ボヘミアン・ラプソディ\",\"folders\":[],\"genres\":[{\"genrecode\":6}],\"network\":1,\"ch\":\"200\",\"datetime\":1571400000,\"duration\":8082,\"newflag\":true,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":65,\"recording\":false,\"portableEnable\":false},{\"num\":2,\"id\":\"5dcc7ce00004\",\"titlename\":\"同期のサクラ#06[解][字][デ]\",\"folders\":[],\"genres\":[{\"genrecode\":3}],\"network\":0,\"ch\":\"101\",\"datetime\":1573650000,\"duration\":3135,\"newflag\":true,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":0,\"recording\":false,\"portableEnable\":false},{\"num\":3,\"id\":\"5dc342600007\",\"titlename\":\"同期のサクラ#05[解][字][デ]\",\"folders\":[],\"genres\":[{\"genrecode\":3}],\"network\":0,\"ch\":\"101\",\"datetime\":1573045200,\"duration\":3105,\"newflag\":true,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":0,\"recording\":false,\"portableEnable\":false},{\"num\":4,\"id\":\"5dba07e00008\",\"titlename\":\"同期のサクラ#04[解][字][デ]\",\"folders\":[],\"genres\":[{\"genrecode\":3}],\"network\":0,\"ch\":\"101\",\"datetime\":1572440400,\"duration\":3105,\"newflag\":true,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":0,\"recording\":false,\"portableEnable\":false},{\"num\":5,\"id\":\"5db0da440009\",\"titlename\":\"同期のサクラ#03[解][字][デ]\",\"folders\":[],\"genres\":[{\"genrecode\":3}],\"network\":0,\"ch\":\"101\",\"datetime\":1571838900,\"duration\":3104,\"newflag\":true,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":0,\"recording\":false,\"portableEnable\":false},{\"num\":6,\"id\":\"5da792e0000b\",\"titlename\":\"同期のサクラ#02[解][字][デ]\",\"folders\":[],\"genres\":[{\"genrecode\":3}],\"network\":0,\"ch\":\"101\",\"datetime\":1571230800,\"duration\":3070,\"newflag\":true,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":0,\"recording\":false,\"portableEnable\":false},{\"num\":7,\"id\":\"5d9e5860000d\",\"titlename\":\"同期のサクラ#01[解][字][デ]\",\"folders\":[],\"genres\":[{\"genrecode\":3}],\"network\":0,\"ch\":\"101\",\"datetime\":1570626000,\"duration\":3070,\"newflag\":false,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":0,\"recording\":false,\"portableEnable\":false},{\"num\":8,\"id\":\"5dd1f4180013\",\"titlename\":\"[生]2019 F1グランプリ 第20戦 ブラジルGP 決勝\",\"folders\":[],\"genres\":[{\"genrecode\":1}],\"network\":2,\"ch\":\"309\",\"datetime\":1574008200,\"duration\":9009,\"newflag\":false,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":65,\"recording\":false,\"portableEnable\":false},{\"num\":9,\"id\":\"5dd83e400012\",\"titlename\":\"[生]F1 GPニュース 2019 ~ブラジルGP特集~\",\"folders\":[],\"genres\":[{\"genrecode\":1}],\"network\":2,\"ch\":\"309\",\"datetime\":1574420400,\"duration\":3590,\"newflag\":true,\"autorecflag\":true,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":2,\"recmode\":65,\"recording\":false,\"portableEnable\":false},{\"num\":10,\"id\":\"5dd8f5100019\",\"titlename\":\"2019 F1グランプリ 第20戦 ブラジルGP 予選\",\"folders\":[],\"genres\":[{\"genrecode\":1}],\"network\":2,\"ch\":\"309\",\"datetime\":1574467200,\"duration\":4806,\"newflag\":false,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":2,\"recmode\":65,\"recording\":false,\"portableEnable\":false},{\"num\":11,\"id\":\"5dd5b760000f\",\"titlename\":\"同期のサクラ#07[解][字][デ]\",\"folders\":[],\"genres\":[{\"genrecode\":3}],\"network\":0,\"ch\":\"101\",\"datetime\":1574254800,\"duration\":3097,\"newflag\":true,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":0,\"recmode\":66,\"recording\":false,\"portableEnable\":false},{\"num\":12,\"id\":\"5de1a2f0000c\",\"titlename\":\"全力!脱力タイムズ[字]【新木優子&アンタ柴田うわぁぁぁーダメだっての巻】\",\"folders\":[],\"genres\":[{\"genrecode\":5}],\"network\":0,\"ch\":\"081\",\"datetime\":1575036000,\"duration\":2399,\"newflag\":false,\"autorecflag\":false,\"autodelflag\":false,\"protection\":false,\"mochidashiState\":2,\"recmode\":66,\"recording\":false,\"portableEnable\":false}],\"is_rating_title\":false}}";
1705+ }
1706+
1707+ if (response == null) {
1708+ errmsg = ERRID+ERRMSG_NORESPONSE;
1709+ return(null);
1710+ }
1711+
1712+ // 先頭部分をチェックする
1713+ Matcher mh = Pattern.compile("\\{\"NETdeNAVI\"").matcher(response);
1714+ if ( ! mh.find()) {
1715+ errmsg = ERRID+ERRMSG_INVALIDRESPONSE;
1716+ return(null);
1717+ }
1718+
1719+ // 応答メッセージをパースする
1720+ // {"result":24,"reason":"ERR_EXCLUSIVE"}
1721+ Matcher mr = Pattern.compile("\\{" +
1722+ "\"result\":(\\d+)," +
1723+ "\"reason\":\"([^\"]*)\"\\}").matcher(response);
1724+
1725+ if ( !mr.find() )
1726+ break;
1727+
1728+ String result = mr.group(1);
1729+ String reason = mr.group(2);
1730+
1731+ if (result.equals("1")){
1732+ if (mountedOnly){
1733+ reportProgress(MSGID+"ドライブがマウントされていないので取得を中止します:"+device_id);
1734+ return(null);
1735+ }
1736+
1737+ if (mountUsbDrive(device_id))
1738+ continue;
1739+
1740+ return(null);
1741+ }
1742+
1743+ errmsg = "録画タイトルの取得に失敗しました。(result=" + result + ",reason=" + reason + ")";
1744+ reportProgress(errmsg);
1745+ return(null);
1746+ }
1747+
1748+ ArrayList<TitleInfo> list = new ArrayList<TitleInfo>();
1749+
1750+ // {"num":4,"id":"51a6a4900016","titlename":"[映][SS]スティーヴン・キング 骨の袋[前編]",
1751+ // "folders":[{"folder_id":9}],"genres":[{"genrecode":15}],"ch":"BS201",
1752+ // "datetime":1369843200,"duration":4858,"newflag":true,"autorecflag":false},
1753+ Matcher ma = Pattern.compile("\\{" +
1754+ "\"num\":(\\d+)," + // 1
1755+ "\"id\":\"([^\"]+)\"," + // 2
1756+ "\"titlename\":\"([^\"]+)\"," + // 3
1757+ "\"folders\":\\[([^\\]]*)\\]," + // 4
1758+ "\"genres\":\\[([^\\]]*)\\]," + // 5
1759+ "\"network\":(\\d+)," + // 6
1760+ "\"ch\":\"([^\"]+)\"," + // 7
1761+ "\"datetime\":(\\d+)," + // 8
1762+ "\"duration\":(\\d+)," + // 9
1763+ "\"newflag\":(false|true)," + // 10
1764+ "\"autorecflag\":(false|true)," + // 11
1765+ "\"autodelflag\":(false|true)," + // 12
1766+ "\"protection\":(false|true)," + // 13
1767+ "\"mochidashiState\":(\\d+)," + // 14
1768+ "\"recmode\":(\\d+)," + // 15
1769+ "\"recording\":(false|true)," + // 16
1770+ "\"portableEnable\":(false|true)" + // 17
1771+ "\\}").matcher(response);
1772+
1773+ int serial = 0;
1774+
1775+ while ( ma.find() ) {
1776+ // 個々のデータを取り出す
1777+ TitleInfo entry = new TitleInfo();
1778+
1779+ String id = ma.group(2);
1780+ String titlename = unescapeJavaString(ma.group(3));
1781+ String folders = ma.group(4);
1782+ String genres = ma.group(5);
1783+ String network = ma.group(6);
1784+ String ch = ma.group(7);
1785+ String datetime = ma.group(8);
1786+ String duration = ma.group(9);
1787+ String recmode = ma.group(15);
1788+ Boolean recording = ma.group(16).equals("true");
1789+// String newflag = ma.group(9);
1790+// String autorecflag = ma.group(10);
1791+
1792+ // タイトルID
1793+ entry.setId(id);
1794+
1795+ // すでに同じIDのタイトルがある場合はその情報を引き継ぐ
1796+ TitleInfo tiOld = getTitleInfo(id);
1797+ if (tiOld != null)
1798+ entry = tiOld.clone();
1799+
1800+ // 基本情報をセットする
1801+ entry.setSerial(++serial);
1802+ setTitleBasicInfo(entry, titlename, ch, datetime, duration, device_id, folders, genres);
1803+
1804+ // 録画モード
1805+ String recmode_name = value2text(vrate, recmode);
1806+ entry.setRec_mode(recmode_name);
1807+
1808+
1809+ entry.setRecording(recording);
1810+
1811+ // 予約情報を保存
1812+ list.add(entry.clone());
1813+ }
1814+
1815+ list.sort(new TitleInfoComparator());
1816+
1817+ return(list);
1818+ }
1819+
1820+ /***
1821+ * タイトルの基本情報をセットする
1822+ */
1823+ protected void setTitleBasicInfo(TitleInfo entry, String titlename,
1824+ String ch, String datetime, String duration, String device_id, String folders, String genres){
1825+
1826+ // 開始日、終了日
1827+ int nbsecs = Integer.parseInt(datetime); // 開始日時(UNIX時間/1000)
1828+ int secs = Integer.parseInt(duration); // 録画時間(sec)
1829+ int nesecs = nbsecs + secs; // 終了日時(UNIX時間/1000)
1830+ Date bdate = new Date(nbsecs*1000L); // 開始日時(Date)
1831+ Date edate = new Date(nesecs*1000L); // 終了日時(Date)
1832+
1833+ SimpleDateFormat sfdm = new SimpleDateFormat("yyyy/MM/dd HH:mm", Locale.JAPAN);
1834+ entry.setStartDateTime(sfdm.format(bdate));
1835+ entry.setEndDateTime(sfdm.format(edate));
1836+
1837+ // 開始、終了時刻
1838+ SimpleDateFormat sfh = new SimpleDateFormat("HH");
1839+ SimpleDateFormat sfm = new SimpleDateFormat("mm");
1840+ String ahh = sfh.format(bdate);
1841+ String amm = sfm.format(bdate);
1842+ String zhh = sfh.format(edate);
1843+ String zmm = sfm.format(edate);
1844+ entry.setAhh(ahh);
1845+ entry.setAmm(amm);
1846+ entry.setZhh(zhh);
1847+ entry.setZmm(zmm);
1848+
1849+
1850+ // 次の録画日などを計算する
1851+ SimpleDateFormat sfd = new SimpleDateFormat("yyyy/MM/dd(E)", Locale.JAPAN);
1852+ entry.setRec_date(sfd.format(bdate));
1853+ entry.setRec_min(String.valueOf(secs/60));
1854+
1855+ // タイトル
1856+ entry.setTitle(titlename);
1857+
1858+ // チャンネル
1859+ entry.setChannel(ch);
1860+
1861+ // デバイス
1862+ String device_name = value2text(device, device_id);
1863+ entry.setRec_device(device_name);
1864+
1865+ // フォルダー
1866+ entry.setRec_folder(parseFolders(device_id, folders));
1867+
1868+ // ジャンル
1869+ entry.setRec_genre(parseGenres(genres));
1870+ }
1871+
1872+ /*
1873+ * タイトル詳細情報を取得する
1874+ */
1875+ protected boolean getTitleDetail( TitleInfo t) {
1876+
1877+ String id = t.getId();
1878+ String device_name = t.getRec_device();
1879+ String device_id = text2value(device, device_name);
1880+ String title_id = t.getId();
1881+ String str = "drive_id=" + device_id + "&title_id=" + title_id;
1882+ String response = "";
1883+
1884+ for (int n=0; n<3; n++){
1885+ // おまじない
1886+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
1887+
1888+ if (!test){
1889+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/titlelist/LoadTitleDetailData.php?" + str, null, thisEncoding);
1890+// String header = d[0];
1891+ response= d[1];
1892+ }
1893+ else{
1894+ response = "{\"NETdeNAVI\":{\"titleDetailInfo\":{\"id\":\"5daa27d0000a\",\"unique_id\":\"5daa27d0000a\",\"titlename\":\"【最新作】[映][SS]ボヘミアン・ラプソディ\",\"folders\":[],\"network\":1,\"chNum\":\"200\",\"branchNum\":0,\"branchNumExists\":false,\"ch\":\"BS200\",\"recmode\":{\"id\":65,\"name\":\"AN\"},\"genres\":[{\"genrecode\":6}],\"datetime\":1571400000,\"duration\":8082,\"copycount\":1,\"dlnaObjectID\":\"RD_0001000A_0000010300\",\"rating\":0,\"recording\":false,\"chapter\":[{\"chaptername\":\"\",\"duration\":8082,\"changeFlag\":0}],\"autodel\":false,\"protection\":false,\"mochidashiContentId\":\"0\",\"resumeTime\":0,\"shortTimeInfo\":[{\"mode\":0,\"playtime\":8040},{\"mode\":2,\"playtime\":6180},{\"mode\":3,\"playtime\":300}],\"detailDescription\":\"【メガヒット9】【『ボヘミアン・ラプソディ』とクイーンの世界】アカデミー賞4部門受賞の大ヒット作TV初放送!\r\n \r\n≪詳細情報≫\r\n番組概要\r\n伝説のバンド「クイーン」の栄光と苦闘をヴォーカル、フレディ・マーキュリーの短くも波瀾に満ちた人生を軸に描く音楽伝記映画。\r\nみどころ\r\n伝説のバンド「クイーン」の栄光とその裏に秘められた苦闘を、リードヴォーカルのフレディ・マーキュリーの短くも波瀾に満ちた人生を軸に描いた音楽伝記映画。バンドメンバーのブライアン・メイとロジャー・テイラー全面協力の下、28曲にも及ぶクイーンの楽曲を使用。21分に渡るクライマックスのライヴ・エイドの再現場面は圧巻。フレディ役のラミ・マレックが主演男優賞に輝いたほか、アカデミー賞で4部門を受賞。\r\n監督/出演\r\n監督:ブライアン・シンガー\r出演:ラミ・マレック/ジョゼフ・マゼッロ/ベン・ハーディ/グウィリム・リー/ルーシー・ボイントン\r\nストーリー\r\n1970年、ロンドン。複雑な生い立ちを持つ青年フレディ・マーキュリーは、馴染みのライブハウスで、バンドのヴォーカル脱退に悩むブライアン・メイ、ロジャー・テイラーと出会う。歌唱力を見込まれ、ヴォーカルに抜擢されたフレディにジョン・ディーコンを加えた4人は、「クイーン」のバンド名で活動を開始。やがて、シングル曲「キラー・クイーン」の大ヒットをきっかけに、人気バンドへと成長していくクイーンだったが…。\r\n製作年など\r\nボヘミアン・ラプソディ\r製作:2018年 英=米\r本編時間:145分\r\n再放送日\r\n10/20(日) 21:00 スター3[吹]\r10/22(火) 12:00 スター1\r10/24(木) 13:20 スター3[吹]\r10/31(木) 19:10 スター3[吹]\r\",\"profiles\":[{\"personal_name\":\"グウィリム・リー\"},{\"personal_name\":\"ジョゼフ・マゼッロ\"},{\"personal_name\":\"ジョン・ディーコン\"},{\"personal_name\":\"ブライアン・シンガー\"},{\"personal_name\":\"ブライアン・メイ\"},{\"personal_name\":\"ベン・ハーディ\"},{\"personal_name\":\"ラミ・マレック\"},{\"personal_name\":\"ロジャー・テイラー\"}],\"mochidashiState\":0,\"defective\":false,\"portableEnable\":false}}}";
1895+ }
1896+
1897+ if (response == null) {
1898+ errmsg = ERRID+ERRMSG_NORESPONSE;
1899+ System.out.println(errmsg);
1900+ return(false);
1901+ }
1902+
1903+ // 先頭部分をチェックする
1904+ Matcher mh = Pattern.compile("\\{\"NETdeNAVI\"").matcher(response);
1905+ if ( ! mh.find()) {
1906+ errmsg = ERRID+ERRMSG_INVALIDRESPONSE;
1907+ return (false);
1908+ }
1909+
1910+ // 応答メッセージをパースする
1911+ // {"result":24,"reason":"ERR_EXCLUSIVE"}
1912+ Matcher mr = Pattern.compile("\\{" +
1913+ "\"result\":(\\d+)," +
1914+ "\"reason\":\"([^\"]*)\"\\}").matcher(response);
1915+
1916+ if ( !mr.find() )
1917+ break;
1918+
1919+ String result = mr.group(1);
1920+ String reason = mr.group(2);
1921+
1922+ if (result.equals("1")){
1923+ if (mountUsbDrive(device_id))
1924+ continue;
1925+ }
1926+
1927+ errmsg = "タイトル詳細情報の取得に失敗しました。(result=" + result + ",reason=" + reason + ")";
1928+ reportProgress(errmsg);
1929+ return(false);
1930+ }
1931+
1932+ // {"NETdeNAVI":{"titleDetailInfo":{"id":"589285bc000b","unique_id":"589285bc000b",
1933+ // "titlename":"この素晴らしい世界に祝福を!2 第4話「この貴族の令嬢に良縁を!」",
1934+ // "folders":[{"folder_id":7}],"ch":"091","recmode":"DR","genres":[{"genrecode":7}],
1935+ // "datetime":1485965100,"duration":1807,"copycount":10,"dlnaObjectID":"RD_0001000B_0000010200",
1936+ // "recording":false,
1937+ // "chapter":[{"chaptername":"@","duration":39,"changeFlag":0},
1938+ // {"chaptername":"","duration":168,"changeFlag":0},{"chaptername":"@","duration":61,"changeFlag":0},
1939+ // {"chaptername":"","duration":509,"changeFlag":0},{"chaptername":"@","duration":61,"changeFlag":0},
1940+ // {"chaptername":"","duration":708,"changeFlag":0},{"chaptername":"@","duration":61,"changeFlag":0},
1941+ // {"chaptername":"","duration":17,"changeFlag":0},{"chaptername":"@","duration":179,"changeFlag":0}]}}}
1942+ Matcher ma = Pattern.compile("\\{" +
1943+ "\"id\":\"([^\"]+)\"," + // 1
1944+ "\"unique_id\":\"([^\"]+)\"," + // 2
1945+ "\"titlename\":\"([^\"]+)\"," + // 3
1946+ "\"folders\":\\[([^\\]]*)\\]," + // 4
1947+ "\"network\":(\\d+)," + // 5
1948+ "\"chNum\":\"([^\"]+)\"," + // 6
1949+ "\"branchNum\":(\\d+)," + // 7
1950+ "\"branchNumExists\":(false|true)," +// 8
1951+ "\"ch\":\"([^\"]+)\"," + // 9
1952+ "\"recmode\":\\{\"id\":(\\d+),\"name\":\"([^\"]+)\"\\}," + // 10,11
1953+ "\"genres\":\\[([^\\]]*)\\]," + // 12
1954+ "\"datetime\":(\\d+)," + // 13
1955+ "\"duration\":(\\d+)," + // 14
1956+ "\"copycount\":(\\d+)," + // 15
1957+ "\"dlnaObjectID\":\"([^\"]*)\"," + // 16
1958+ "\"rating\":(\\d+)," + // 17
1959+ "\"recording\":(false|true)," + // 18
1960+ "\"chapter\":\\[([^\\]]*)\\]," + // 19
1961+ "\"autodel\":(false|true)," + // 20
1962+ "\"protection\":(false|true)," + // 21
1963+ "\"mochidashiContentId\":\"([^\"]*)\"," + // 22
1964+ "\"resumeTime\":(\\d+)," + // 23
1965+ "\"shortTimeInfo\":\\[([^\\]]*)\\]," +// 24
1966+ "\"detailDescription\":\"([^\"]+)\"," + // 25
1967+ "\"profiles\":\\[([^\\]]*)\\]," + // 26
1968+ "\"mochidashiState\":(\\d+)," + // 27
1969+ "\"defective\":(false|true)," + // 28
1970+ "\"portableEnable\":(false|true)" + // 29
1971+ "\\}")
1972+ .matcher(response);
1973+
1974+ if ( !ma.find() ) {
1975+ reportProgress(ERRID+"タイトル詳細情報が取得できません.ID=" + id);
1976+ return (false);
1977+ }
1978+
1979+ String unique_id = ma.group(2);
1980+ String titlename = unescapeJavaString(ma.group(3));
1981+ String folders = ma.group(4);
1982+ String ch = ma.group(9);
1983+ String recmode = ma.group(11);
1984+ String genres = ma.group(12);
1985+ String datetime = ma.group(13);
1986+ String duration = ma.group(14);
1987+ String copycount = ma.group(15);
1988+ String dlnaObjectID = ma.group(16);
1989+ Boolean recording = ma.group(18).equals("true");
1990+// String recording = ma.group(12);
1991+ String chapter = ma.group(19);
1992+ String portableEnable = ma.group(29);
1993+
1994+ // 基本情報をセットする
1995+ setTitleBasicInfo(t, titlename, ch, datetime, duration, device_id, folders, genres);
1996+
1997+ // チャプター情報
1998+ t.setChapter(parseChapters(chapter));
1999+ t.setContentId(unique_id);
2000+
2001+ // 録画モード
2002+// String recmode_name = value2text(vrate, recmode);
2003+ t.setRec_mode("[" + recmode + "]");
2004+
2005+ // それ以外の詳細情報
2006+ HashMap<String,String> hmap = new HashMap<String,String>();
2007+ hmap.put("copycount", copycount);
2008+ hmap.put("dlnaObjectID", dlnaObjectID);
2009+ t.setHidden_params(hmap);
2010+
2011+ t.setRecording(recording);
2012+
2013+ t.setDetailLoaded(true);
2014+
2015+ return(true);
2016+ }
2017+
2018+ /**
2019+ * フォルダーのJSONテキストを解析する
2020+ */
2021+ protected ArrayList<TextValueSet> parseFolders(String device_id, String s) {
2022+
2023+ ArrayList<TextValueSet> list = new ArrayList<TextValueSet>();
2024+
2025+ // "folders":[{"folder_id":9}],
2026+ Matcher ma = Pattern.compile("\\{" +
2027+ "\"folder_id\":(\\d+)\\}") // 1
2028+ .matcher(s);
2029+
2030+ while ( ma.find() ) {
2031+ String folder_id = device_id + ":" + ma.group(1);
2032+ String folder_name = value2text(folder, folder_id);
2033+
2034+ TextValueSet t = new TextValueSet();
2035+ t.setText(folder_name);
2036+ t.setValue(folder_id);
2037+ list.add(t);
2038+ }
2039+
2040+ return(list);
2041+ }
2042+
2043+ /**
2044+ * ジャンルのJSONテキストを解析する
2045+ */
2046+ protected ArrayList<TextValueSet> parseGenres(String s) {
2047+
2048+ ArrayList<TextValueSet> list = new ArrayList<TextValueSet>();
2049+
2050+ // "genres":[{"genrecode":7}],
2051+ Matcher ma = Pattern.compile("\\{" +
2052+ "\"genrecode\":(\\d+)\\}") // 1
2053+ .matcher(s);
2054+
2055+ while ( ma.find() ) {
2056+ String genre_code = Integer.toHexString(Integer.parseInt(ma.group(1))).toUpperCase();
2057+ ProgGenre pg = ProgGenre.getByIEPG(genre_code);
2058+ String genre_name = pg != null ? pg.toString() : "";
2059+
2060+ TextValueSet t = new TextValueSet();
2061+ t.setText(genre_name);
2062+ t.setValue(genre_code);
2063+ list.add(t);
2064+ }
2065+
2066+ return(list);
2067+ }
2068+
2069+ /**
2070+ * チャプターのJSONテキストを解析する
2071+ */
2072+ protected ArrayList<ChapterInfo> parseChapters(String s) {
2073+
2074+ ArrayList<ChapterInfo> list = new ArrayList<ChapterInfo>();
2075+
2076+ // {"chaptername":"","duration":168,"changeFlag":0},
2077+ Matcher ma = Pattern.compile("\\{" +
2078+ "\"chaptername\":\"([^\"]*)\"," +
2079+ "\"duration\":(\\d+)," +
2080+ "\"changeFlag\":(\\d+)\\}")
2081+ .matcher(s);
2082+
2083+ while ( ma.find() ) {
2084+ String chaptername = unescapeJavaString(ma.group(1));
2085+ String duration = ma.group(2);
2086+ String changeFlag = ma.group(3);
2087+
2088+ ChapterInfo c = new ChapterInfo();
2089+ c.setName(chaptername);
2090+ c.setDuration(Integer.parseInt(duration));
2091+ c.setChangeFlag(changeFlag.equals("1"));
2092+
2093+ list.add(c);
2094+ }
2095+
2096+ return(list);
2097+ }
2098+
2099+ /**
2100+ * タイトルの編集をレコーダに通知する。これを先に呼ばないとタイトル情報更新時にエラーになる
2101+ */
2102+ private boolean notifyTitleEdit(String devid, String ttlid) {
2103+ errmsg = "";
2104+
2105+ // おまじない
2106+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
2107+
2108+ // RDへの情報作成
2109+ String pstr = "device_id=" + devid + "&title_id=" + ttlid;
2110+
2111+ // RDへ情報送信
2112+ reportProgress(MSGID+"レコーダーにタイトル編集を通知します.");
2113+ String [] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/titlelist/NotifyTitleEdit.php?" + pstr, null, thisEncoding);
2114+// String header = d[0];
2115+ String response = d[1];
2116+
2117+ if ( !checkGeneralResponse( "タイトル編集通知", response) ){
2118+ return(false);
2119+ }
2120+
2121+ return(true);
2122+ }
2123+
2124+ /*
2125+ * タイトル更新要求のPOSTデータを生成する
2126+ */
2127+ protected String createTitlePostData(TitleInfo t, TitleInfo o, String devId) {
2128+ String postData = "";
2129+ try {
2130+ String title = o != null && t.getTitle().equals(o.getTitle()) ? TITLE_NOCHANGED :
2131+ // ARIB外字を変換した文字列があれば元の外字に戻す
2132+ URLEncoder.encode(AribCharMap.ConvStringToArib(t.getTitle()), thisEncoding);
2133+
2134+ postData =
2135+ "drive_id=" + devId + "&" +
2136+ "title_id=" + t.getId() + "&" +
2137+ "titleName=" + title + "&" +
2138+ encodeFolders(t.getRec_folder()) + "&" +
2139+ "folder_change=1&" +
2140+ encodeChapters(t.getChapter());
2141+ } catch (UnsupportedEncodingException e) {
2142+ // TODO 自動生成された catch ブロック
2143+ e.printStackTrace();
2144+ }
2145+
2146+ return postData;
2147+ }
2148+
2149+ /**
2150+ * フォルダー情報をJSONから変換したPOSTデータの形式にエンコードする
2151+ */
2152+ protected String encodeFolders(ArrayList<TextValueSet> tvs){
2153+ String s = "";
2154+ int n=0;
2155+ for (TextValueSet t : tvs){
2156+ if ( !s.equals("") )
2157+ s += "&";
2158+ s += "folders[" + String.valueOf(n) + "][folder_id]=" + extractFolderID( t.getValue() );
2159+ n++;
2160+ }
2161+
2162+ return s;
2163+ }
2164+
2165+ /**
2166+ * チャプター情報をJSONから変換したPOSTデータの形式にエンコードする
2167+ */
2168+ protected String encodeChapters(ArrayList<ChapterInfo> ci){
2169+ String s = "";
2170+ int n=0;
2171+ for (ChapterInfo t : ci){
2172+ if ( !s.equals("") )
2173+ s += "&";
2174+ try {
2175+ s += "chapters[" + String.valueOf(n) + "][chnum]=" + String.valueOf(n) + "&" +
2176+ "chapters[" + String.valueOf(n) + "][chname]=" + URLEncoder.encode(t.getName(), thisEncoding);
2177+ } catch (UnsupportedEncodingException e) {
2178+ // TODO 自動生成された catch ブロック
2179+ e.printStackTrace();
2180+ }
2181+ n++;
2182+ }
2183+
2184+ return s;
2185+ }
2186+
2187+ /*
2188+ * 固定の各種設定情報を初期化する
2189+ */
2190+ protected void setSettingFixed() {
2191+ // 録画設定
2192+ setSettingVrate(vrate);
2193+
2194+ // パターン情報
2195+ setSettingPattern(tvsPatternCode, tvsPatternName);
2196+
2197+ // エンコーダー
2198+ setSettingEncoder(encoder);
2199+
2200+ // 持ち出し情報
2201+ setSettingPortable(portable);
2202+ }
2203+
2204+ /*
2205+ * 録画モードを初期化する
2206+ */
2207+ protected void setSettingVrate(ArrayList<TextValueSet> tvs) {
2208+ // {"str":"DR","val":0}, {"str":"AF","val":64}, {"str":"AN","val":65}, {"str":"AS","val":66},
2209+ // {"str":"AL","val":67}, {"str":"AE","val":68},
2210+ // {"str":"XP","val":32}, {"str":"SP","val":33}, {"str":"LP","val":34}, {"str":"EP","val":35},
2211+ // {"str":"自動(HD 4.7GB)","val":192}, {"str":"自動(HD 8.5GB)","val":193}, {"str":"自動(HD 25GB)","val":194},
2212+ // {"str":"自動(HD 50GB)","val":195}, {"str":"自動(標準 4.7GB)","val":197}
2213+
2214+ String texts[] = {
2215+ RECMODE_NAME_DR, "[AF]", "[AN]", "[AS]", "[AL]", "[AE]",
2216+ "[XP]", "[SP]", "[LP]", "[EP]",
2217+ "[自動(HD 4.7GB)]", "[自動(HD 8.5GB)]", "[自動(HD 25GB)]", "[自動(HD 50GB)]",
2218+ "[自動(標準 4.7GB)]",
2219+ null};
2220+ String values[] = {
2221+ RECMODE_DR, "64", "65", "66", "67", "68",
2222+ "32", "33", "34", "35",
2223+ "192", "193", "194", "195", "197",
2224+ null};
2225+
2226+ tvs.clear();
2227+
2228+ for (int n=0; values[n] != null; n++){
2229+ TextValueSet t = new TextValueSet();
2230+ t.setText(texts[n]);
2231+ t.setValue(values[n]);
2232+ tvs.add(t);
2233+ }
2234+ }
2235+
2236+ /*
2237+ * 繰り返しパターンを初期化する
2238+ */
2239+ protected void setSettingPattern(ArrayList<TextValueSet> tvsC, ArrayList<TextValueSet> tvsN) {
2240+ // {"num":0,"str":"しない"},{"num":1,"str":"毎週日"},{"num":2,"str":"毎週月"},{"num":4,"str":"毎週火"},
2241+ // {"num":8,"str":"毎週水"},{"num":16,"str":"毎週木"},{"num":32,"str":"毎週金"},{"num":64,"str":"毎週土"},
2242+ // {"num":62,"str":"月~金"},{"num":126,"str":"月~土"},{"num":124,"str":"火~土"},{"num":127,"str":"毎日"}
2243+ int codesRd[]={
2244+ 0, 1, 2, 3, 4, 5, RPTPTN_ID_SAT,
2245+ RPTPTN_ID_MON2THU, RPTPTN_ID_MON2FRI, RPTPTN_ID_MON2SAT, RPTPTN_ID_TUE2SAT, RPTPTN_ID_EVERYDAY, -1};
2246+ String codes[] = {
2247+ "1", "2", "4", "8", "16", "32", "64",
2248+ "30", "62", "126", "124", "127", null};
2249+ String names[] = {
2250+ "毎日曜日", "毎月曜日", "毎火曜日", "毎水曜日", "毎木曜日", "毎金曜日", "毎土曜日",
2251+ "毎月~木", "毎月~金", "毎月~土", "毎火~土", "毎日", null};
2252+
2253+ tvsC.clear();
2254+ tvsN.clear();
2255+
2256+ for (int n=0; codes[n] != null; n++){
2257+ TextValueSet tc = new TextValueSet();
2258+ tc.setText(String.valueOf(codesRd[n]));
2259+ tc.setValue(codes[n]);
2260+ tvsC.add(tc);
2261+
2262+ TextValueSet tn = new TextValueSet();
2263+ tn.setText(names[n]);
2264+ tn.setValue(codes[n]);
2265+ tvsN.add(tn);
2266+ }
2267+ }
2268+
2269+ /**
2270+ * エンコーダー情報を自動生成する
2271+ */
2272+ protected void setSettingEncoder(ArrayList<TextValueSet> tvs) {
2273+ tvs.clear();
2274+
2275+ // チューナー情報を自動生成する
2276+ if (getTunerNum() >= 2){
2277+ for (int i=1; i<=getTunerNum(); i++){
2278+ TextValueSet t = new TextValueSet();
2279+ t.setText("R" + i);
2280+ t.setValue("R" + i);
2281+ tvs.add(t);
2282+ }
2283+ }
2284+ }
2285+
2286+ /*
2287+ * 持ち出し情報を初期化する
2288+ */
2289+ protected void setSettingPortable(ArrayList<TextValueSet> tvs) {
2290+ // {"portableId":0,"portableStr":"しない"},{"portableId":1,"portableStr":"スマホ持ち出し"},
2291+ // {"portableId":3,"portableStr":"DVD持ち出し(VR)"},{"portableId":2,"portableStr":"SeeQVault対応SDカード転送"}
2292+ String codes[] = {
2293+ MOCHIDASHI_NONE, "1", "3", "2", null};
2294+ String names[] = {
2295+ "しない", "スマホ持ち出し", "DVD持ち出し(VR)", "SeeQVault対応SDカード転送", null};
2296+
2297+ tvs.clear();
2298+
2299+ for (int n=0; codes[n] != null; n++){
2300+ TextValueSet t = new TextValueSet();
2301+ t.setText(names[n]);
2302+ t.setValue(codes[n]);
2303+ tvs.add(t);
2304+ }
2305+ }
2306+
2307+ /*
2308+ * 各種設定情報をレコーダーから取得する
2309+ */
2310+ protected boolean setSettingVariable() {
2311+ // 設定情報
2312+// ArrayList<TextValueSet>discs = new ArrayList<TextValueSet>();
2313+// ArrayList<TextValueSet>repeats = new ArrayList<TextValueSet>();
2314+// setSettingSelect(repeats, discs, folder, vrate);
2315+
2316+ // 記録先デバイス
2317+ setSettingDevice();
2318+
2319+ // フォルダ一覧
2320+ setSettingFolders();
2321+
2322+ // チャンネルコードバリュー - uva、bsaは廃止 -
2323+ ArrayList<TextValueSet> tvsCV = new ArrayList<TextValueSet>();
2324+ ArrayList<TextValueSet> tvsCT = new ArrayList<TextValueSet>();
2325+ ArrayList<TextValueSet> tvsBR = new ArrayList<TextValueSet>();
2326+ setSettingChCodeValue(tvsCV, tvsCT, tvsBR);
2327+ if ( tvsCV.size() == 0 && tvsCT.size() == 0 && tvsBR.size() == 0) {
2328+ System.err.println(errmsg = ERRID+"【致命的エラー】 チャンネルコードバリューが取得できません");
2329+ return (false);
2330+ }
2331+ chvalue = tvsCV;
2332+ chtype = tvsCT;
2333+ tvsBranch = tvsBR;
2334+
2335+ return (true);
2336+ }
2337+
2338+ /*
2339+ * デバイス情報を取得する
2340+ */
2341+ protected boolean setSettingDevice() {
2342+ ArrayList<TextValueSet>tvsD = new ArrayList<TextValueSet>();
2343+ ArrayList<DeviceInfo>tvsDI = new ArrayList<DeviceInfo>();
2344+
2345+ reportProgress(MSGID+"ドライブ一覧を取得します.");
2346+
2347+ // おまじない
2348+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
2349+
2350+ String res = "";
2351+
2352+ if (!test){
2353+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/titlelist/LoadSelectInfo.php",null, thisEncoding);
2354+ res = d[1];
2355+ }
2356+ else{
2357+ res = "{\"NETdeNAVI\":{\"drive_select\":[{\"drive_id\":0,\"drivename\":\"HDD\",\"drivetype\":0,\"playlist_enable\":true,\"folder_enable\":true,\"allsize\":1897272,\"freesize\":394712,\"connected\":true,\"protected\":false,\"mounted\":true,\"ready\":true,\"pin_setting\":false,\"formatType\":12},{\"drive_id\":1,\"drivename\":\"ディスクなし\",\"drivetype\":5,\"playlist_enable\":false,\"folder_enable\":false,\"allsize\":0,\"freesize\":0,\"connected\":true,\"protected\":true,\"mounted\":true,\"ready\":false,\"pin_setting\":false,\"formatType\":12},{\"drive_id\":2,\"drivename\":\"タイムシフトマシン\",\"drivetype\":0,\"playlist_enable\":false,\"folder_enable\":false,\"allsize\":1536000,\"freesize\":0,\"connected\":true,\"protected\":false,\"mounted\":true,\"ready\":true,\"pin_setting\":false,\"formatType\":12}],\"network_channel_list\":[{\"network\":\"地上\",\"service\":[{\"channelid\":\"G7D30B000\",\"channelNr\":\"011-0\",\"channelName\":\"NHK総合1・奈良\",\"chnum\":\"011\",\"branch\":0,\"branchNumExists\":1,\"networkId\":32048,\"serviceId\":45056,\"multiSub\":false},{\"channelid\":\"G7D30B001\",\"channelNr\":\"012-0\",\"channelName\":\"NHK総合2・奈良\",\"chnum\":\"012\",\"branch\":0,\"branchNumExists\":1,\"networkId\":32048,\"serviceId\":45057,\"multiSub\":true},{\"channelid\":\"G7D70A000\",\"channelNr\":\"011-1\",\"channelName\":\"NHK総合1・大阪\",\"chnum\":\"011\",\"branch\":1,\"branchNumExists\":1,\"networkId\":32112,\"serviceId\":40960,\"multiSub\":false},{\"channelid\":\"G7D70A001\",\"channelNr\":\"012-1\",\"channelName\":\"NHK総合2・大阪\",\"chnum\":\"012\",\"branch\":1,\"branchNumExists\":1,\"networkId\":32112,\"serviceId\":40961,\"multiSub\":true},{\"channelid\":\"G7FD10808\",\"channelNr\":\"021\",\"channelName\":\"NHKEテレ1大阪\",\"chnum\":\"021\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32721,\"serviceId\":2056,\"multiSub\":false},{\"channelid\":\"G7FD10809\",\"channelNr\":\"022\",\"channelName\":\"NHKEテレ2大阪\",\"chnum\":\"022\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32721,\"serviceId\":2057,\"multiSub\":true},{\"channelid\":\"G7FD1080A\",\"channelNr\":\"023\",\"channelName\":\"NHKEテレ3大阪\",\"chnum\":\"023\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32721,\"serviceId\":2058,\"multiSub\":true},{\"channelid\":\"G7FD20810\",\"channelNr\":\"041\",\"channelName\":\"MBS毎日放送\",\"chnum\":\"041\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32722,\"serviceId\":2064,\"multiSub\":false},{\"channelid\":\"G7FD20811\",\"channelNr\":\"042\",\"channelName\":\"MBS毎日放送2\",\"chnum\":\"042\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32722,\"serviceId\":2065,\"multiSub\":true},{\"channelid\":\"G7FD20812\",\"channelNr\":\"043\",\"channelName\":\"MBS毎日放送3\",\"chnum\":\"043\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32722,\"serviceId\":2066,\"multiSub\":true},{\"channelid\":\"G7D66A430\",\"channelNr\":\"051\",\"channelName\":\"KBS京都\",\"chnum\":\"051\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32102,\"serviceId\":42032,\"multiSub\":false},{\"channelid\":\"G7FD30818\",\"channelNr\":\"061\",\"channelName\":\"ABCテレビ1\",\"chnum\":\"061\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32723,\"serviceId\":2072,\"multiSub\":false},{\"channelid\":\"G7FD30819\",\"channelNr\":\"062\",\"channelName\":\"ABCテレビ2\",\"chnum\":\"062\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32723,\"serviceId\":2073,\"multiSub\":true},{\"channelid\":\"G7FD3081A\",\"channelNr\":\"063\",\"channelName\":\"ABCテレビ3\",\"chnum\":\"063\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32723,\"serviceId\":2074,\"multiSub\":true},{\"channelid\":\"G7D76A030\",\"channelNr\":\"071\",\"channelName\":\"テレビ大阪1\",\"chnum\":\"071\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32118,\"serviceId\":41008,\"multiSub\":false},{\"channelid\":\"G7D76A031\",\"channelNr\":\"072\",\"channelName\":\"テレビ大阪2\",\"chnum\":\"072\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32118,\"serviceId\":41009,\"multiSub\":true},{\"channelid\":\"G7D76A032\",\"channelNr\":\"073\",\"channelName\":\"テレビ大阪3\",\"chnum\":\"073\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32118,\"serviceId\":41010,\"multiSub\":true},{\"channelid\":\"G7FD40820\",\"channelNr\":\"081\",\"channelName\":\"関西テレビ1\",\"chnum\":\"081\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32724,\"serviceId\":2080,\"multiSub\":false},{\"channelid\":\"G7FD40821\",\"channelNr\":\"082\",\"channelName\":\"関西テレビ2\",\"chnum\":\"082\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32724,\"serviceId\":2081,\"multiSub\":true},{\"channelid\":\"G7FD40822\",\"channelNr\":\"083\",\"channelName\":\"関西テレビ3\",\"chnum\":\"083\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32724,\"serviceId\":2082,\"multiSub\":true},{\"channelid\":\"G7D36B030\",\"channelNr\":\"091\",\"channelName\":\"奈良テレビ1\",\"chnum\":\"091\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32054,\"serviceId\":45104,\"multiSub\":false},{\"channelid\":\"G7D36B031\",\"channelNr\":\"092\",\"channelName\":\"奈良テレビ2\",\"chnum\":\"092\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32054,\"serviceId\":45105,\"multiSub\":true},{\"channelid\":\"G7FD50828\",\"channelNr\":\"101\",\"channelName\":\"読売テレビ1\",\"chnum\":\"101\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32725,\"serviceId\":2088,\"multiSub\":false},{\"channelid\":\"G7FD50829\",\"channelNr\":\"102\",\"channelName\":\"読売テレビ2\",\"chnum\":\"102\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32725,\"serviceId\":2089,\"multiSub\":true},{\"channelid\":\"G7FD5082A\",\"channelNr\":\"103\",\"channelName\":\"読売テレビ3\",\"chnum\":\"103\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32725,\"serviceId\":2090,\"multiSub\":true},{\"channelid\":\"G7D3FB078\",\"channelNr\":\"111\",\"channelName\":\"KCNファミリー\",\"chnum\":\"111\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32063,\"serviceId\":45176,\"multiSub\":false},{\"channelid\":\"G7D3FB079\",\"channelNr\":\"112\",\"channelName\":\"KCNファミリー2\",\"chnum\":\"112\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32063,\"serviceId\":45177,\"multiSub\":true},{\"channelid\":\"G7D3DB068\",\"channelNr\":\"121\",\"channelName\":\"スポーツ&カルチャー\",\"chnum\":\"121\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32061,\"serviceId\":45160,\"multiSub\":false},{\"channelid\":\"G7D3DB069\",\"channelNr\":\"122\",\"channelName\":\"スポーツ&カルチャー\",\"chnum\":\"122\",\"branch\":0,\"branchNumExists\":0,\"networkId\":32061,\"serviceId\":45161,\"multiSub\":true}]},{\"network\":\"BS\",\"service\":[{\"channelid\":\"G00040065\",\"channelNr\":\"101\",\"channelName\":\"NHKBS1\",\"chnum\":\"101\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":101,\"multiSub\":false},{\"channelid\":\"G00040066\",\"channelNr\":\"102\",\"channelName\":\"NHKBS1\",\"chnum\":\"102\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":102,\"multiSub\":true},{\"channelid\":\"G00040067\",\"channelNr\":\"103\",\"channelName\":\"NHKBSプレミアム\",\"chnum\":\"103\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":103,\"multiSub\":false},{\"channelid\":\"G00040068\",\"channelNr\":\"104\",\"channelName\":\"NHKBSプレミアム\",\"chnum\":\"104\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":104,\"multiSub\":true},{\"channelid\":\"G0004008D\",\"channelNr\":\"141\",\"channelName\":\"BS日テレ\",\"chnum\":\"141\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":141,\"multiSub\":false},{\"channelid\":\"G0004008E\",\"channelNr\":\"142\",\"channelName\":\"BS日テレ\",\"chnum\":\"142\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":142,\"multiSub\":true},{\"channelid\":\"G0004008F\",\"channelNr\":\"143\",\"channelName\":\"BS日テレ\",\"chnum\":\"143\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":143,\"multiSub\":true},{\"channelid\":\"G00040097\",\"channelNr\":\"151\",\"channelName\":\"BS朝日1\",\"chnum\":\"151\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":151,\"multiSub\":false},{\"channelid\":\"G00040098\",\"channelNr\":\"152\",\"channelName\":\"BS朝日2\",\"chnum\":\"152\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":152,\"multiSub\":true},{\"channelid\":\"G00040099\",\"channelNr\":\"153\",\"channelName\":\"BS朝日3\",\"chnum\":\"153\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":153,\"multiSub\":true},{\"channelid\":\"G000400A1\",\"channelNr\":\"161\",\"channelName\":\"BS-TBS\",\"chnum\":\"161\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":161,\"multiSub\":false},{\"channelid\":\"G000400A2\",\"channelNr\":\"162\",\"channelName\":\"BS-TBS\",\"chnum\":\"162\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":162,\"multiSub\":true},{\"channelid\":\"G000400A3\",\"channelNr\":\"163\",\"channelName\":\"BS-TBS\",\"chnum\":\"163\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":163,\"multiSub\":true},{\"channelid\":\"G000400AB\",\"channelNr\":\"171\",\"channelName\":\"BSテレ東\",\"chnum\":\"171\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":171,\"multiSub\":false},{\"channelid\":\"G000400AC\",\"channelNr\":\"172\",\"channelName\":\"BSテレ東2\",\"chnum\":\"172\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":172,\"multiSub\":true},{\"channelid\":\"G000400AD\",\"channelNr\":\"173\",\"channelName\":\"BSテレ東3\",\"chnum\":\"173\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":173,\"multiSub\":true},{\"channelid\":\"G000400B5\",\"channelNr\":\"181\",\"channelName\":\"BSフジ・181\",\"chnum\":\"181\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":181,\"multiSub\":false},{\"channelid\":\"G000400B6\",\"channelNr\":\"182\",\"channelName\":\"BSフジ・182\",\"chnum\":\"182\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":182,\"multiSub\":true},{\"channelid\":\"G000400B7\",\"channelNr\":\"183\",\"channelName\":\"BSフジ・183\",\"chnum\":\"183\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":183,\"multiSub\":true},{\"channelid\":\"G000400BF\",\"channelNr\":\"191\",\"channelName\":\"WOWOWプライム\",\"chnum\":\"191\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":191,\"multiSub\":false},{\"channelid\":\"G000400C0\",\"channelNr\":\"192\",\"channelName\":\"WOWOWライブ\",\"chnum\":\"192\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":192,\"multiSub\":false},{\"channelid\":\"G000400C1\",\"channelNr\":\"193\",\"channelName\":\"WOWOWシネマ\",\"chnum\":\"193\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":193,\"multiSub\":false},{\"channelid\":\"G000400C8\",\"channelNr\":\"200\",\"channelName\":\"スターチャンネル1\",\"chnum\":\"200\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":200,\"multiSub\":false},{\"channelid\":\"G000400C9\",\"channelNr\":\"201\",\"channelName\":\"スターチャンネル2\",\"chnum\":\"201\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":201,\"multiSub\":false},{\"channelid\":\"G000400CA\",\"channelNr\":\"202\",\"channelName\":\"スターチャンネル3\",\"chnum\":\"202\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":202,\"multiSub\":true},{\"channelid\":\"G000400D3\",\"channelNr\":\"211\",\"channelName\":\"BS11イレブン\",\"chnum\":\"211\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":211,\"multiSub\":false},{\"channelid\":\"G000400DE\",\"channelNr\":\"222\",\"channelName\":\"BS12トゥエルビ\",\"chnum\":\"222\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":222,\"multiSub\":false},{\"channelid\":\"G000400E7\",\"channelNr\":\"231\",\"channelName\":\"BSキャンパスex\",\"chnum\":\"231\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":231,\"multiSub\":false},{\"channelid\":\"G000400E8\",\"channelNr\":\"232\",\"channelName\":\"BSキャンパスon\",\"chnum\":\"232\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":232,\"multiSub\":true},{\"channelid\":\"G000400EA\",\"channelNr\":\"234\",\"channelName\":\"グリーンチャンネル\",\"chnum\":\"234\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":234,\"multiSub\":false},{\"channelid\":\"G000400EC\",\"channelNr\":\"236\",\"channelName\":\"BSアニマックス\",\"chnum\":\"236\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":236,\"multiSub\":false},{\"channelid\":\"G000400EE\",\"channelNr\":\"238\",\"channelName\":\"FOXスポーツエンタ\",\"chnum\":\"238\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":238,\"multiSub\":false},{\"channelid\":\"G000400F1\",\"channelNr\":\"241\",\"channelName\":\"BSスカパー!\",\"chnum\":\"241\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":241,\"multiSub\":false},{\"channelid\":\"G000400F2\",\"channelNr\":\"242\",\"channelName\":\"J SPORTS 1\",\"chnum\":\"242\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":242,\"multiSub\":false},{\"channelid\":\"G000400F3\",\"channelNr\":\"243\",\"channelName\":\"J SPORTS 2\",\"chnum\":\"243\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":243,\"multiSub\":false},{\"channelid\":\"G000400F4\",\"channelNr\":\"244\",\"channelName\":\"J SPORTS 3\",\"chnum\":\"244\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":244,\"multiSub\":false},{\"channelid\":\"G000400F5\",\"channelNr\":\"245\",\"channelName\":\"J SPORTS 4\",\"chnum\":\"245\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":245,\"multiSub\":false},{\"channelid\":\"G000400FB\",\"channelNr\":\"251\",\"channelName\":\"BS釣りビジョン\",\"chnum\":\"251\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":251,\"multiSub\":false},{\"channelid\":\"G000400FC\",\"channelNr\":\"252\",\"channelName\":\"シネフィルWOWOW\",\"chnum\":\"252\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":252,\"multiSub\":false},{\"channelid\":\"G000400FF\",\"channelNr\":\"255\",\"channelName\":\"日本映画専門ch\",\"chnum\":\"255\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":255,\"multiSub\":false},{\"channelid\":\"G00040100\",\"channelNr\":\"256\",\"channelName\":\"ディズニーch\",\"chnum\":\"256\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":256,\"multiSub\":false},{\"channelid\":\"G00040102\",\"channelNr\":\"258\",\"channelName\":\"ディーライフ\",\"chnum\":\"258\",\"branch\":0,\"branchNumExists\":0,\"networkId\":4,\"serviceId\":258,\"multiSub\":false}]},{\"network\":\"CS\",\"service\":[{\"channelid\":\"G00060001\",\"channelNr\":\"001\",\"channelName\":\"-\",\"chnum\":\"001\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":1,\"multiSub\":false},{\"channelid\":\"G00060037\",\"channelNr\":\"055\",\"channelName\":\"ショップチャンネル\",\"chnum\":\"055\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":55,\"multiSub\":false},{\"channelid\":\"G00070064\",\"channelNr\":\"100\",\"channelName\":\"-\",\"chnum\":\"100\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":100,\"multiSub\":false},{\"channelid\":\"G000700A1\",\"channelNr\":\"161\",\"channelName\":\"QVC\",\"chnum\":\"161\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":161,\"multiSub\":false},{\"channelid\":\"G000600DA\",\"channelNr\":\"218\",\"channelName\":\"東映チャンネル\",\"chnum\":\"218\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":218,\"multiSub\":false},{\"channelid\":\"G000600DB\",\"channelNr\":\"219\",\"channelName\":\"衛星劇場\",\"chnum\":\"219\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":219,\"multiSub\":false},{\"channelid\":\"G000700DF\",\"channelNr\":\"223\",\"channelName\":\"映画・chNECO\",\"chnum\":\"223\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":223,\"multiSub\":false},{\"channelid\":\"G000700E3\",\"channelNr\":\"227\",\"channelName\":\"ザ・シネマ\",\"chnum\":\"227\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":227,\"multiSub\":false},{\"channelid\":\"G000700F0\",\"channelNr\":\"240\",\"channelName\":\"ムービープラス\",\"chnum\":\"240\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":240,\"multiSub\":false},{\"channelid\":\"G000700FA\",\"channelNr\":\"250\",\"channelName\":\"スカイA\",\"chnum\":\"250\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":250,\"multiSub\":false},{\"channelid\":\"G000700FE\",\"channelNr\":\"254\",\"channelName\":\"GAORA\",\"chnum\":\"254\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":254,\"multiSub\":false},{\"channelid\":\"G00070101\",\"channelNr\":\"257\",\"channelName\":\"日テレジータス\",\"chnum\":\"257\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":257,\"multiSub\":false},{\"channelid\":\"G00070106\",\"channelNr\":\"262\",\"channelName\":\"ゴルフネットワーク\",\"chnum\":\"262\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":262,\"multiSub\":false},{\"channelid\":\"G00070122\",\"channelNr\":\"290\",\"channelName\":\"SKY STAGE\",\"chnum\":\"290\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":290,\"multiSub\":false},{\"channelid\":\"G00070124\",\"channelNr\":\"292\",\"channelName\":\"時代劇専門ch\",\"chnum\":\"292\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":292,\"multiSub\":false},{\"channelid\":\"G00070125\",\"channelNr\":\"293\",\"channelName\":\"ファミリー劇場\",\"chnum\":\"293\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":293,\"multiSub\":false},{\"channelid\":\"G00070126\",\"channelNr\":\"294\",\"channelName\":\"ホームドラマCH\",\"chnum\":\"294\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":294,\"multiSub\":false},{\"channelid\":\"G00070127\",\"channelNr\":\"295\",\"channelName\":\"MONDO TV\",\"chnum\":\"295\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":295,\"multiSub\":false},{\"channelid\":\"G00060128\",\"channelNr\":\"296\",\"channelName\":\"TBSチャンネル1\",\"chnum\":\"296\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":296,\"multiSub\":false},{\"channelid\":\"G00070129\",\"channelNr\":\"297\",\"channelName\":\"TBSチャンネル2\",\"chnum\":\"297\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":297,\"multiSub\":false},{\"channelid\":\"G0006012A\",\"channelNr\":\"298\",\"channelName\":\"テレ朝チャンネル1\",\"chnum\":\"298\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":298,\"multiSub\":false},{\"channelid\":\"G0006012B\",\"channelNr\":\"299\",\"channelName\":\"テレ朝チャンネル2\",\"chnum\":\"299\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":299,\"multiSub\":false},{\"channelid\":\"G0007012C\",\"channelNr\":\"300\",\"channelName\":\"日テレプラス\",\"chnum\":\"300\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":300,\"multiSub\":false},{\"channelid\":\"G0007012D\",\"channelNr\":\"301\",\"channelName\":\"エンタメ~テレ\",\"chnum\":\"301\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":301,\"multiSub\":false},{\"channelid\":\"G00070131\",\"channelNr\":\"305\",\"channelName\":\"銀河◆歴ドラ・サスペ\",\"chnum\":\"305\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":305,\"multiSub\":false},{\"channelid\":\"G00070133\",\"channelNr\":\"307\",\"channelName\":\"フジテレビONE\",\"chnum\":\"307\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":307,\"multiSub\":false},{\"channelid\":\"G00070134\",\"channelNr\":\"308\",\"channelName\":\"フジテレビTWO\",\"chnum\":\"308\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":308,\"multiSub\":false},{\"channelid\":\"G00070135\",\"channelNr\":\"309\",\"channelName\":\"フジテレビNEXT\",\"chnum\":\"309\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":309,\"multiSub\":false},{\"channelid\":\"G00070136\",\"channelNr\":\"310\",\"channelName\":\"スーパー!ドラマTV\",\"chnum\":\"310\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":310,\"multiSub\":false},{\"channelid\":\"G00070137\",\"channelNr\":\"311\",\"channelName\":\"AXN 海外ドラマ\",\"chnum\":\"311\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":311,\"multiSub\":false},{\"channelid\":\"G00070138\",\"channelNr\":\"312\",\"channelName\":\"FOX\",\"chnum\":\"312\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":312,\"multiSub\":false},{\"channelid\":\"G0007013A\",\"channelNr\":\"314\",\"channelName\":\"女性ch/LaLa\",\"chnum\":\"314\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":314,\"multiSub\":false},{\"channelid\":\"G0007013C\",\"channelNr\":\"316\",\"channelName\":\"AXNミステリー\",\"chnum\":\"316\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":316,\"multiSub\":false},{\"channelid\":\"G0006013D\",\"channelNr\":\"317\",\"channelName\":\"KBS World\",\"chnum\":\"317\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":317,\"multiSub\":false},{\"channelid\":\"G0006013E\",\"channelNr\":\"318\",\"channelName\":\"Mnet\",\"chnum\":\"318\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":318,\"multiSub\":false},{\"channelid\":\"G00070141\",\"channelNr\":\"321\",\"channelName\":\"スペシャプラス\",\"chnum\":\"321\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":321,\"multiSub\":false},{\"channelid\":\"G00070142\",\"channelNr\":\"322\",\"channelName\":\"スペースシャワーTV\",\"chnum\":\"322\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":322,\"multiSub\":false},{\"channelid\":\"G00070143\",\"channelNr\":\"323\",\"channelName\":\"MTV\",\"chnum\":\"323\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":323,\"multiSub\":false},{\"channelid\":\"G00070144\",\"channelNr\":\"324\",\"channelName\":\"ミュージック・エア\",\"chnum\":\"324\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":324,\"multiSub\":false},{\"channelid\":\"G00070145\",\"channelNr\":\"325\",\"channelName\":\"エムオン!\",\"chnum\":\"325\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":325,\"multiSub\":false},{\"channelid\":\"G00070149\",\"channelNr\":\"329\",\"channelName\":\"歌謡ポップス\",\"chnum\":\"329\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":329,\"multiSub\":false},{\"channelid\":\"G0007014A\",\"channelNr\":\"330\",\"channelName\":\"キッズステーション\",\"chnum\":\"330\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":330,\"multiSub\":false},{\"channelid\":\"G0007014B\",\"channelNr\":\"331\",\"channelName\":\"カートゥーン\",\"chnum\":\"331\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":331,\"multiSub\":false},{\"channelid\":\"G0007014D\",\"channelNr\":\"333\",\"channelName\":\"AT-X\",\"chnum\":\"333\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":333,\"multiSub\":false},{\"channelid\":\"G00060153\",\"channelNr\":\"339\",\"channelName\":\"ディズニージュニア\",\"chnum\":\"339\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":339,\"multiSub\":false},{\"channelid\":\"G00070154\",\"channelNr\":\"340\",\"channelName\":\"ディスカバリー\",\"chnum\":\"340\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":340,\"multiSub\":false},{\"channelid\":\"G00070155\",\"channelNr\":\"341\",\"channelName\":\"アニマルプラネット\",\"chnum\":\"341\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":341,\"multiSub\":false},{\"channelid\":\"G00070156\",\"channelNr\":\"342\",\"channelName\":\"ヒストリーチャンネル\",\"chnum\":\"342\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":342,\"multiSub\":false},{\"channelid\":\"G00070157\",\"channelNr\":\"343\",\"channelName\":\"ナショジオ\",\"chnum\":\"343\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":343,\"multiSub\":false},{\"channelid\":\"G0006015D\",\"channelNr\":\"349\",\"channelName\":\"日テレNEWS24\",\"chnum\":\"349\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":349,\"multiSub\":false},{\"channelid\":\"G0007015F\",\"channelNr\":\"351\",\"channelName\":\"TBS NEWS\",\"chnum\":\"351\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":351,\"multiSub\":false},{\"channelid\":\"G00070161\",\"channelNr\":\"353\",\"channelName\":\"BBCワールド\",\"chnum\":\"353\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":353,\"multiSub\":false},{\"channelid\":\"G00070162\",\"channelNr\":\"354\",\"channelName\":\"CNNj\",\"chnum\":\"354\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":354,\"multiSub\":false},{\"channelid\":\"G0007016B\",\"channelNr\":\"363\",\"channelName\":\"囲碁・将棋チャンネル\",\"chnum\":\"363\",\"branch\":0,\"branchNumExists\":0,\"networkId\":7,\"serviceId\":363,\"multiSub\":false},{\"channelid\":\"G00060320\",\"channelNr\":\"800\",\"channelName\":\"スカサカ! \",\"chnum\":\"800\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":800,\"multiSub\":false},{\"channelid\":\"G00060321\",\"channelNr\":\"801\",\"channelName\":\"スカチャン1\",\"chnum\":\"801\",\"branch\":0,\"branchNumExists\":0,\"networkId\":6,\"serviceId\":801,\"multiSub\":false}]}]}}";
2358+ }
2359+
2360+ if (res == null) {
2361+ errmsg = ERRID+ERRMSG_NORESPONSE;
2362+ return (false);
2363+ }
2364+
2365+ //{"drive_id":0,"drivename":"HDD","drivetype":0,"playlist_enable":true,"folder_enable":true,
2366+ // "allsize":1897740,"freesize":1187093,"connected":true,"canwrite":true,"protected":false,
2367+ // "mounted":true,"ready":true,"formatType":12},
2368+ Matcher ma = Pattern.compile("\\{" +
2369+ "\"drive_id\":(\\d+)," + // 1
2370+ "\"drivename\":\"([^\"]+)\"," + // 2
2371+ "\"drivetype\":(\\d+)," + // 3
2372+ "\"playlist_enable\":(true|false)," + // 4
2373+ "\"folder_enable\":(true|false)," + // 5
2374+ "\"allsize\":(\\d+)," + // 6
2375+ "\"freesize\":(\\d+)," + // 7
2376+ "\"connected\":(true|false)," + // 8
2377+ "\"protected\":(true|false)," + // 9
2378+ "\"mounted\":(true|false)," + // 10
2379+ "\"ready\":(true|false)," + // 11
2380+ "\"pin_setting\":(true|false)," + // 12
2381+ "\"formatType\":(\\d+)" + // 13
2382+ "\\}").matcher(res);
2383+
2384+ int allsizeALL = 0;
2385+ int freesizeALL = 0;
2386+
2387+ while ( ma.find() ) {
2388+ String drive_id = ma.group(1);
2389+ String drive_name = ma.group(2);
2390+ String drive_type = ma.group(3);
2391+ boolean playlist_enable = ma.group(4).equals("true");
2392+ boolean folder_enable = ma.group(5).equals("true");
2393+ int allsize = Integer.parseInt(ma.group(6));
2394+ int freesize = Integer.parseInt(ma.group(7));
2395+ boolean connected = ma.group(8).equals("true");
2396+ boolean isprotected = ma.group(9).equals("true");
2397+ boolean mounted = ma.group(10).equals("true");
2398+ boolean ready = ma.group(11).equals("true");
2399+ boolean pin_setting = ma.group(12).equals("true");
2400+ int formatType = Integer.parseInt(ma.group(13));
2401+
2402+ TextValueSet t = new TextValueSet();
2403+ t.setValue(drive_id);
2404+ // デバイス名のコロン以降は無視する
2405+ t.setText(GetDevicePrefix(drive_name));
2406+ tvsD.add(t);
2407+
2408+ DeviceInfo di = new DeviceInfo();
2409+ di.setId(drive_id);
2410+ di.setName(drive_name);
2411+ di.setType(drive_type);
2412+ di.setPlaylistEnable(playlist_enable);
2413+ di.setFolderEnable(folder_enable);
2414+ di.setAllSize(allsize);
2415+ di.setFreeSize(freesize);
2416+ di.setFreeMin((int)Math.round((double)freesize/MIN_PER_MB));
2417+ di.setConnected(connected);
2418+ di.setProtected(isprotected);
2419+ di.setMounted(mounted);
2420+ di.setReady(ready);
2421+ di.setFormatType(formatType);
2422+ tvsDI.add(di);
2423+
2424+ allsizeALL += allsize;
2425+ freesizeALL += freesize;
2426+ }
2427+
2428+ TextValueSet t = new TextValueSet();
2429+ t.setValue(DEVICE_ALL);
2430+ t.setText(DEVICE_NAME_ALL);
2431+ tvsD.add(t);
2432+
2433+ DeviceInfo di = new DeviceInfo();
2434+ di.setId(DEVICE_ALL);
2435+ di.setName(DEVICE_NAME_ALL);
2436+ di.setAllSize(allsizeALL);
2437+ di.setFreeSize(freesizeALL);
2438+ di.setFreeMin((int)Math.round((double)freesizeALL/MIN_PER_MB));
2439+ tvsDI.add(di);
2440+
2441+ device = tvsD;
2442+ setDeviceInfos(tvsDI);
2443+
2444+ return (true);
2445+ }
2446+
2447+ /*
2448+ * デバイス情報を保存する
2449+ */
2450+ protected void saveDeviceInfos(){
2451+ String myFileId = getIPAddr()+"_"+getPortNo()+"_"+getRecorderId();
2452+ devinfoTFile = "env/devinfo."+myFileId+".xml";
2453+
2454+ DeviceInfosToFile(getDeviceInfos(), devinfoTFile);
2455+ }
2456+ /*
2457+ * フォルダ一覧を取得する
2458+ */
2459+ protected boolean setSettingFolders() {
2460+ ArrayList<TextValueSet> tvsD = device;
2461+ ArrayList<TextValueSet> tvsF = new ArrayList<TextValueSet>();
2462+
2463+ reportProgress(MSGID+"フォルダ一覧を取得します.");
2464+
2465+ TextValueSet t0 = new TextValueSet();
2466+ t0.setText(FOLDER_NAME_NONE);
2467+ t0.setValue(FOLDER_NONE);
2468+ tvsF.add(t0);
2469+
2470+ for (int n=0; n<tvsD.size(); n++){
2471+ TextValueSet tvs = tvsD.get(n);
2472+
2473+ // RDへの情報作成
2474+ String drive_id = tvs.getValue();
2475+ String pstr = "drive_id=" + drive_id;
2476+
2477+ // おまじない
2478+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
2479+
2480+ // RDへ情報送信
2481+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/ReloadFolderInfo.php?" + pstr, null, thisEncoding);
2482+ String res = d[1];
2483+
2484+ if (res == null) {
2485+ errmsg = ERRID+ERRMSG_NORESPONSE;
2486+ return (false);
2487+ }
2488+
2489+ Matcher ma = Pattern.compile("\\{" +
2490+ "\"folder_id\":(\\d+)," + // 1
2491+ "\"str\":\"([^\"]+)\"\\}") // 2
2492+ .matcher(res);
2493+
2494+ while ( ma.find() ) {
2495+ TextValueSet t = new TextValueSet();
2496+
2497+ String device_name = tvs.getText();
2498+ String folder_id = ma.group(1);
2499+ String folder_name = "[" + device_name + "] " + unescapeJavaString(ma.group(2));
2500+
2501+ t.setText(folder_name);
2502+ t.setValue(drive_id + ":" + folder_id);
2503+ tvsF.add(t);
2504+ }
2505+ }
2506+
2507+ folder = tvsF;
2508+
2509+ return (true);
2510+ }
2511+
2512+ /*
2513+ * フォルダー一覧をファイルに保存する
2514+ */
2515+ private void saveFolders(){
2516+ String myFileId = getIPAddr()+"_"+getPortNo()+"_"+getRecorderId();
2517+ folderTFile = "env/folders."+myFileId+".xml";
2518+
2519+ TVSsave(folder, folderTFile);
2520+ }
2521+
2522+ /*
2523+ * タイトル一覧をファイルに保存する
2524+ */
2525+ private void saveTitles(String devId){
2526+ String myFileId = getIPAddr()+"_"+getPortNo()+"_"+getRecorderId();
2527+
2528+ if (!DEVICE_ID.equals(DEVICE_ALL)){
2529+ titleFile = "env/title."+myFileId+"." + DEVICE_ID + ".xml";
2530+
2531+ TitlesToFile(getTitles(), titleFile);
2532+ }
2533+ else{
2534+ for (DeviceInfo di : getDeviceInfos() ) {
2535+ if (di.getId().equals(DEVICE_ALL))
2536+ continue;
2537+
2538+ ArrayList<TitleInfo> list = new ArrayList<TitleInfo>();
2539+
2540+ for (TitleInfo ti : getTitles()){
2541+ if (di.getName().startsWith(ti.getRec_device()))
2542+ list.add(ti);
2543+ }
2544+
2545+ titleFile = "env/title."+myFileId+"." + di.getId() + ".xml";
2546+ TitlesToFile(list, titleFile);
2547+ }
2548+ }
2549+ }
2550+
2551+ /*
2552+ * チャンネル一覧を取得する
2553+ */
2554+ private void setSettingChCodeValue(ArrayList<TextValueSet> tvsvalue, ArrayList<TextValueSet> tvstype,
2555+ ArrayList<TextValueSet> tvsbranch) {
2556+ tvsvalue.clear();
2557+ tvstype.clear();
2558+ tvsbranch.clear();
2559+
2560+ reportProgress(MSGID+"チャンネル一覧を取得します.");
2561+
2562+ // おまじない
2563+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
2564+
2565+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/LoadChannelList.php",null, thisEncoding);
2566+ String res = d[1];
2567+
2568+ if (res == null) {
2569+ errmsg = ERRID+ERRMSG_NORESPONSE;
2570+ return;
2571+ }
2572+
2573+ // {"network":"地上","service":[... ]}
2574+ Matcher ma = Pattern.compile("\\{\"network\":\"([^\"]+)\",\"service\":\\[([^\\]]+)\\]\\}").matcher(res);
2575+ while ( ma.find() ) {
2576+ String network = ma.group(1);
2577+ String chlist = ma.group(2);
2578+ String prefix = "";
2579+
2580+ // "uvd","bsd","csd","l1","l2","l3"
2581+ String typ = "";
2582+ switch(network){
2583+ case "地上":
2584+ typ = CHTYPE_UVD;
2585+ break;
2586+ case "BS":
2587+ typ = CHTYPE_BSD;
2588+ prefix = CHPREFIX_BS;
2589+ break;
2590+ case "CS":
2591+ typ = CHTYPE_CSD;
2592+ prefix = CHPREFIX_CS;
2593+ break;
2594+ }
2595+
2596+ // {"channelid":"G7FE00400","channelNr":"011","channelName":"NHK総合1・東京","chnum":"011","branch":0,
2597+ // "branchNumExists":0,"networkId":32736,"serviceId":1024,"multiSub":false},
2598+ Matcher mb = Pattern.compile("\\{" +
2599+ "\"channelid\":\"([^\"]+)\"," + // 1
2600+ "\"channelNr\":\"([^\"]+)\"," + // 2
2601+ "\"channelName\":\"([^\"]+)\"," + // 3
2602+ "\"chnum\":\"([^\"]+)\"," + // 4
2603+ "\"branch\":(\\d+)," + // 5
2604+ "\"branchNumExists\":(\\d+)," + // 6
2605+ "\"networkId\":(\\d+)," + // 7
2606+ "\"serviceId\":(\\d+)," + // 8
2607+ "\"multiSub\":([^}]+)}") // 9
2608+ .matcher(chlist);
2609+
2610+ // var uvd_ch_text = new Array(
2611+ // "011-1",
2612+ // "012-1",
2613+ // var uvd_ch_value = new Array(
2614+ // "011:32736:1024",
2615+ // "012:32736:1025",
2616+
2617+ while ( mb.find() ) {
2618+ String chno = prefix + mb.group(2);
2619+ String chid = mb.group(4) + ":" + mb.group(7) + ":" + mb.group(8);
2620+
2621+ TextValueSet t = new TextValueSet();
2622+ t.setText(chno);
2623+ t.setValue(chid);
2624+ tvsvalue.add(t);
2625+
2626+ TextValueSet x = new TextValueSet();
2627+ x.setText(chid);
2628+ x.setValue(typ);
2629+ tvstype.add(x);
2630+
2631+ TextValueSet b = new TextValueSet();
2632+ b.setText(chid);
2633+ b.setValue(mb.group(5));
2634+ tvsbranch.add(b);
2635+ }
2636+ }
2637+ }
2638+
2639+ /*
2640+ * 録画設定情報を取得する(未使用)
2641+ */
2642+ protected void setSettingSelect(ArrayList<TextValueSet> tvsR, ArrayList<TextValueSet> tvsD,
2643+ ArrayList<TextValueSet> tvsF, ArrayList<TextValueSet> tvsV) {
2644+ tvsR.clear();
2645+ tvsD.clear();
2646+ tvsF.clear();
2647+ tvsV.clear();
2648+
2649+ reportProgress(MSGID+"録画設定情報を取得します.");
2650+
2651+ // おまじない
2652+ Authenticator.setDefault(new MyAuthenticator(getUser(), getPasswd()));
2653+
2654+ String[] d = reqGET("http://"+getIPAddr()+":"+getPortNo()+"/torunavi/LoadDialogInitializationData.php?schId=0", null, thisEncoding);
2655+ String res = d[1];
2656+
2657+ if (res == null) {
2658+ errmsg = ERRID+ERRMSG_NORESPONSE;
2659+ return;
2660+ }
2661+
2662+ Matcher ma = Pattern.compile("\\{\"day_select\":\\[([^\\]]+)\\],").matcher(res);
2663+ if ( ma.find() ) {
2664+ Matcher mb = Pattern.compile("\\{\"str\":\"([^\"]+)\",\"val\":\"([^\"]+)\"\\}").matcher(ma.group(1));
2665+
2666+ while( mb.find() ){
2667+ TextValueSet t = new TextValueSet();
2668+ String repeat_id = ma.group(2);
2669+ String repeat_name = ma.group(1);
2670+ t.setValue(repeat_id);
2671+ t.setText(repeat_name);
2672+ tvsR.add(t);
2673+ }
2674+ }
2675+
2676+ Matcher mc = Pattern.compile("\\{\"disc_select\":\\[([^\\]]+)\\],").matcher(res);
2677+ if ( mc.find() ) {
2678+ Matcher md = Pattern.compile("\\{" +
2679+ "\"driveid\":\"([^\"]+)\"," + // 1
2680+ "\"str\":\"([^\"]+)\"," + // 2
2681+ "\"folder_select\":\\[([^\\]]+)\\]," + // 3
2682+ "\"pqmode_select\":\\[([^\\]]+)\\]") // 4
2683+ .matcher(mc.group(1));
2684+
2685+ while( md.find() ){
2686+ String drive_id = md.group(1);
2687+ String drive_name = md.group(2);
2688+ String folders = md.group(3);
2689+ String vrates = md.group(4);
2690+
2691+ TextValueSet t = new TextValueSet();
2692+ t.setValue(drive_id);
2693+ t.setText(drive_name);
2694+ tvsD.add(t);
2695+
2696+ Matcher me = Pattern.compile("\\{" +
2697+ "\"folderid\":\"([^\"]+)\"," + // 1
2698+ "\"str\":\"([^\"]+)\"\\}") // 2
2699+ .matcher(folders);
2700+
2701+ while( me.find() ){
2702+ String folder_id = mc.group(1);
2703+ String folder_name = "[" + drive_name + "] " + unescapeJavaString(me.group(2));
2704+
2705+ TextValueSet tc = new TextValueSet();
2706+ tc.setValue(folder_id);
2707+ tc.setText(folder_name);
2708+ tvsF.add(tc);
2709+ }
2710+
2711+ if (!drive_id.equals(DEVICE_HDD)){
2712+ continue;
2713+ }
2714+
2715+ Matcher mf = Pattern.compile("\\{" +
2716+ "\"str\":\"([^\"]+)\"," + // 1
2717+ "\"pqmode\":\\[([^\\]]+)\\]\\}") // 2
2718+ .matcher(vrates);
2719+
2720+ while( mf.find() ){
2721+ String vrate_id = mf.group(1);
2722+ String vrate_name = "[" + drive_name + "] " + unescapeJavaString(me.group(2));
2723+
2724+ TextValueSet tc = new TextValueSet();
2725+ tc.setValue(vrate_id);
2726+ tc.setText(vrate_name);
2727+ tvsV.add(tc);
2728+ }
2729+ }
2730+ }
2731+ }
2732+
2733+ /*
2734+ * <device_id>:<folder_id> から<folder_id>を切り出す
2735+ */
2736+ protected String extractFolderID( String fol_id ) {
2737+ if (fol_id == null)
2738+ return null;
2739+
2740+ int idx = fol_id.indexOf(':');
2741+ if (idx == -1)
2742+ return fol_id;
2743+
2744+ return fol_id.substring(idx+1);
2745+ }
2746+
2747+ public String unescapeJavaString(String st) {
2748+ StringBuilder sb = new StringBuilder(st.length());
2749+
2750+ for (int i = 0; i < st.length(); i++) {
2751+ char ch = st.charAt(i);
2752+ if (ch == '\\') {
2753+ char nextChar = (i == st.length() - 1) ? '\\' : st
2754+ .charAt(i + 1);
2755+ // Octal escape?
2756+ if (nextChar >= '0' && nextChar <= '7') {
2757+ String code = "" + nextChar;
2758+ i++;
2759+ if ((i < st.length() - 1) && st.charAt(i + 1) >= '0'
2760+ && st.charAt(i + 1) <= '7') {
2761+ code += st.charAt(i + 1);
2762+ i++;
2763+ if ((i < st.length() - 1) && st.charAt(i + 1) >= '0'
2764+ && st.charAt(i + 1) <= '7') {
2765+ code += st.charAt(i + 1);
2766+ i++;
2767+ }
2768+ }
2769+ sb.append((char) Integer.parseInt(code, 8));
2770+ continue;
2771+ }
2772+ switch (nextChar) {
2773+ case '\\':
2774+ ch = '\\';
2775+ break;
2776+ case 'b':
2777+ ch = '\b';
2778+ break;
2779+ case 'f':
2780+ ch = '\f';
2781+ break;
2782+ case 'n':
2783+ ch = '\n';
2784+ break;
2785+ case 'r':
2786+ ch = '\r';
2787+ break;
2788+ case 't':
2789+ ch = '\t';
2790+ break;
2791+ case '\"':
2792+ ch = '\"';
2793+ break;
2794+ case '\'':
2795+ ch = '\'';
2796+ break;
2797+ case '/':
2798+ ch = '/';
2799+ break;
2800+ // Hex Unicode: u????
2801+ case 'u':
2802+ if (i >= st.length() - 5) {
2803+ ch = 'u';
2804+ break;
2805+ }
2806+ int code = Integer.parseInt(
2807+ "" + st.charAt(i + 2) + st.charAt(i + 3)
2808+ + st.charAt(i + 4) + st.charAt(i + 5), 16);
2809+ sb.append(Character.toChars(code));
2810+ i += 5;
2811+ continue;
2812+ }
2813+ i++;
2814+ }
2815+ sb.append(ch);
2816+ }
2817+ return sb.toString();
2818+ }
2819+
2820+ /*
2821+ * デバイス名からコロンの前の部分だけを取り出す
2822+ */
2823+ static String GetDevicePrefix(String device_name){
2824+ Matcher ma = Pattern.compile("^(.*):").matcher(device_name);
2825+ if (ma.find()){
2826+ return ma.group(1);
2827+ }
2828+
2829+ return device_name;
2830+ }
2831+}