001package silicondust.ant.taskdefs;
002/*-
003 * ##########################################################################
004 * TV H/W, EPGs, and Recording
005 * $Id: HDHRTunerTask.java 5999 2020-05-18 16:41:28Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/silicondust/trunk/src/main/java/silicondust/ant/taskdefs/HDHRTunerTask.java $
007 * %%
008 * Copyright (C) 2013 - 2020 Allen D. Ball
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 *      http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * ##########################################################################
022 */
023import ball.swing.table.ArrayListTableModel;
024import ball.swing.table.SimpleTableModel;
025import ball.util.ant.taskdefs.AnnotatedAntTask;
026import ball.util.ant.taskdefs.AntTask;
027import ball.util.ant.taskdefs.ClasspathDelegateAntTask;
028import ball.util.ant.taskdefs.ConfigurableAntTask;
029import java.net.InetAddress;
030import java.util.ArrayList;
031import java.util.List;
032import java.util.Map;
033import java.util.SortedMap;
034import lombok.Getter;
035import lombok.NoArgsConstructor;
036import lombok.Setter;
037import lombok.ToString;
038import lombok.experimental.Accessors;
039import org.apache.tools.ant.BuildException;
040import org.apache.tools.ant.Project;
041import org.apache.tools.ant.Task;
042import org.apache.tools.ant.util.ClasspathUtils;
043import silicondust.Channel;
044import silicondust.ChannelRangeMap;
045import silicondust.HDHRProgram;
046import silicondust.HDHRTuner;
047
048import static java.util.Comparator.comparing;
049import static java.util.Objects.requireNonNull;
050import static java.util.function.Function.identity;
051import static java.util.stream.Collectors.counting;
052import static java.util.stream.Collectors.groupingBy;
053import static lombok.AccessLevel.PROTECTED;
054import static org.apache.commons.lang3.StringUtils.EMPTY;
055
056/**
057 * {@link.uri http://ant.apache.org/ Ant} {@link Task} for HDHomeRun tuners.
058 *
059 * {@ant.task}
060 *
061 * @see Options
062 * @see Scan
063 *
064 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
065 * @version $Revision: 5999 $
066 */
067@AntTask("hdhr-tuner")
068@NoArgsConstructor @ToString
069public class HDHRTunerTask extends Task implements AnnotatedAntTask,
070                                                   ClasspathDelegateAntTask,
071                                                   ConfigurableAntTask {
072    private static final String COLON = ":";
073    private static final String DOT = ".";
074    private static final String EQUALS = "=";
075
076    @Getter @Setter @Accessors(chain = true, fluent = true)
077    private ClasspathUtils.Delegate delegate = null;
078    @Getter
079    private Integer id = null;
080    @Getter
081    private InetAddress address = null;
082    @Getter @Setter
083    private int key = 0;
084    private ArrayList<Directive> list = new ArrayList<>();
085    protected transient HDHRTuner tuner = null;
086
087    public void setID(String id) throws BuildException {
088        try {
089            this.id = Integer.parseInt(id, 16);
090        } catch (BuildException exception) {
091            throw exception;
092        } catch (RuntimeException exception) {
093            throw exception;
094        } catch (Throwable throwable) {
095            throw new BuildException(throwable);
096        }
097    }
098
099    public void setAddress(String address) throws BuildException {
100        try {
101            this.address = InetAddress.getByName(address);
102        } catch (BuildException exception) {
103            throw exception;
104        } catch (RuntimeException exception) {
105            throw exception;
106        } catch (Throwable throwable) {
107            throw new BuildException(throwable);
108        }
109    }
110
111    public List<Directive> getDirectiveList() { return list; }
112    public void addConfiguredGet(Get get) { getDirectiveList().add(get); }
113    public void addConfiguredSet(Set set) { getDirectiveList().add(set); }
114
115    @Override
116    public void init() throws BuildException {
117        super.init();
118        ClasspathDelegateAntTask.super.init();
119        ConfigurableAntTask.super.init();
120    }
121
122    @Override
123    public void execute() throws BuildException {
124        super.execute();
125        AnnotatedAntTask.super.execute();
126
127        if (getId() == null && getAddress() == null) {
128            throw new BuildException("Either `id' or `address' attribute must be specified");
129        }
130
131        try {
132            if (getAddress() != null) {
133                tuner = new HDHRTuner(getAddress());
134            } else {
135                tuner = HDHRTuner.discover().get(getId());
136            }
137
138            requireNonNull(tuner, "tuner");
139
140            SimpleTableModel model =
141                new SimpleTableModel(new Object[][] { }, 2);
142
143            for (Directive directive : getDirectiveList()) {
144                if (directive instanceof Set) {
145                    Set set = (Set) directive;
146
147                    tuner.set(getKey(), set.getName(), set.getValue());
148                }
149
150                model.row(directive.getName(),
151                          tuner.get(getKey(), directive.getName()));
152            }
153
154            log(model);
155        } catch (BuildException exception) {
156            throw exception;
157        } catch (Throwable throwable) {
158            String tuner =
159                (getAddress() != null)
160                    ? getAddress().getHostAddress()
161                    : Integer.toString(getId(), 16);
162
163            throw new BuildException("Cannot locate tuner " + tuner,
164                                     throwable);
165        }
166    }
167
168    @NoArgsConstructor(access = PROTECTED)
169    protected static abstract class Directive {
170        @Getter @Setter private String name = null;
171
172        @Override
173        public String toString() { return getName(); }
174    }
175
176    /**
177     * {@link HDHRTunerTask} get {@link Directive}.
178     */
179    @NoArgsConstructor
180    public static class Get extends Directive {
181    }
182
183    /**
184     * {@link HDHRTunerTask} set {@link Directive}.
185     */
186    @NoArgsConstructor
187    public static class Set extends Directive {
188        @Getter @Setter private String value = null;
189
190        @Override
191        public String toString() { return getName() + EQUALS + getValue(); }
192    }
193
194    /**
195     * {@link.uri http://ant.apache.org/ Ant}
196     * {@link org.apache.tools.ant.Task} to get all tag names from a
197     * HDHomeRun tuner.
198     *
199     * {@ant.task}
200     */
201    @AntTask("hdhr-tuner-options")
202    @NoArgsConstructor @ToString
203    public static class Options extends HDHRTunerTask {
204        @Override
205        public void execute() throws BuildException {
206            super.execute();
207
208            try {
209                log(tuner.options().stream());
210            } catch (BuildException exception) {
211                throw exception;
212            } catch (Throwable throwable) {
213                throw new BuildException(throwable);
214            }
215        }
216    }
217
218    /**
219     * {@link.uri http://ant.apache.org/ Ant}
220     * {@link org.apache.tools.ant.Task} to scan a HDHomeRun tuner for
221     * channels.
222     *
223     * {@ant.task}
224     */
225    @AntTask("hdhr-tuner-scan")
226    @NoArgsConstructor @ToString
227    public static class Scan extends HDHRTunerTask {
228        private static final String ENCRYPTED = "encrypted";
229
230        private transient List<HDHRProgram> list = null;
231        private transient ChannelRangeMap rangeMap = null;
232        private transient SortedMap<Integer,Channel> frequencyMap = null;
233
234        @Override
235        public void execute() throws BuildException {
236            super.execute();
237
238            try {
239                long timestamp = System.currentTimeMillis();
240
241                list = tuner.scan();
242                list.removeIf(t -> t.getFlags().contains(ENCRYPTED));
243
244                rangeMap =
245                    list.stream()
246                    .flatMap(t -> Channel.MAP.get(t.getFrequency()).stream())
247                    .map(t -> t.getMapName())
248                    .collect(groupingBy(identity(), counting()))
249                    .entrySet()
250                    .stream()
251                    .max(comparing(Map.Entry::getValue))
252                    .map(t -> ChannelRangeMap.forName(t.getKey()))
253                    .orElse(null);
254
255                if (rangeMap != null) {
256                    frequencyMap = rangeMap.frequencyMap();
257
258                    if (frequencyMap != null) {
259                        list.removeIf(t -> (! frequencyMap.keySet().contains(t.getFrequency())));
260                    }
261
262                    for (int i = 0, n = tuner.getCount(); i < n; i += 1) {
263                        try {
264                            tuner.channelmap(i, 0, rangeMap.name());
265                        } catch (Exception exception) {
266                            log(exception, Project.MSG_WARN);
267                        }
268                    }
269                }
270
271                log();
272                log(new TableModelImpl(list));
273            } catch (BuildException exception) {
274                throw exception;
275            } catch (Throwable throwable) {
276                throw new BuildException(throwable);
277            }
278        }
279
280        private class TableModelImpl extends ArrayListTableModel<HDHRProgram> {
281            private static final long serialVersionUID = -8478080367042692419L;
282
283            public TableModelImpl(Iterable<HDHRProgram> iterable) {
284                super(iterable,
285                      (rangeMap != null) ? rangeMap.name() : EMPTY,
286                      "Frequency", "Program", EMPTY);
287            }
288
289            @Override
290            protected Object getValueAt(HDHRProgram row, int x) {
291                Object object = null;
292
293                switch (x) {
294                case 0:
295                default:
296                    Channel channel =
297                        (frequencyMap != null)
298                            ? frequencyMap.get(row.getFrequency())
299                            : null;
300
301                    if (channel != null) {
302                        object = channel.getNumber();
303                    }
304                    break;
305
306                case 1: object = row.getFrequency();            break;
307                case 2: object = row.getNumber();               break;
308                case 3: object = row.getName();                 break;
309                }
310
311                if (object == null) {
312                    object = EMPTY;
313                }
314
315                return object;
316            }
317        }
318    }
319}