1
2
3
4
5
6
7
8 package com.buckosoft.BuckoVidLib.business;
9
10 import java.io.File;
11 import java.io.FileNotFoundException;
12 import java.io.FileOutputStream;
13 import java.io.IOException;
14 import java.io.UnsupportedEncodingException;
15 import java.net.URLEncoder;
16 import java.text.SimpleDateFormat;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.Comparator;
21 import java.util.Date;
22 import java.util.List;
23 import java.util.Set;
24 import java.util.Timer;
25 import java.util.TimerTask;
26 import java.util.regex.Pattern;
27
28 import org.apache.commons.io.FileUtils;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.springframework.beans.factory.annotation.Autowired;
32 import org.springframework.stereotype.Component;
33
34 import tv.plex.domain.Directory;
35 import tv.plex.domain.MediaContainer;
36
37 import com.buckosoft.BuckoVidLib.db.Database;
38 import com.buckosoft.BuckoVidLib.domain.Actor;
39 import com.buckosoft.BuckoVidLib.domain.Director;
40 import com.buckosoft.BuckoVidLib.domain.Genre;
41 import com.buckosoft.BuckoVidLib.domain.Library;
42 import com.buckosoft.BuckoVidLib.domain.LibrarySection;
43 import com.buckosoft.BuckoVidLib.domain.LibrarySection.Type;
44 import com.buckosoft.BuckoVidLib.domain.TVSeason;
45 import com.buckosoft.BuckoVidLib.domain.TVShow;
46 import com.buckosoft.BuckoVidLib.domain.Video;
47 import com.buckosoft.BuckoVidLib.domain.VideoBase;
48 import com.buckosoft.BuckoVidLib.domain.VideoTexts;
49 import com.buckosoft.BuckoVidLib.domain.Writer;
50 import com.buckosoft.BuckoVidLib.domain.rest.admin.RestRefreshStatus;
51 import com.buckosoft.BuckoVidLib.domain.rest.admin.RestStatusString;
52 import com.buckosoft.BuckoVidLib.util.ConfigManager;
53 import com.buckosoft.BuckoVidLib.util.DomainConverter;
54 import com.buckosoft.BuckoVidLib.web.ImageController;
55 import com.buckosoft.BuckoVidLib.web.PosterController;
56 import com.buckosoft.BuckoVidLib.web.plex.client.ClientFactory;
57 import com.buckosoft.BuckoVidLib.web.plex.client.LibraryService;
58
59
60
61
62
63
64
65
66 @Component
67 public class LibraryManagerImpl implements LibraryManager {
68 private final Log log = LogFactory.getLog(getClass());
69
70 @Autowired
71 private Database db;
72
73 @Autowired
74 private PosterController posterController;
75
76 @Autowired
77 private ImageController imageController;
78
79 private Library library = null;
80 private long lastUpdateTime = 0;
81 private LibraryService libraryService;
82 private List<String> skipSections;
83 private boolean continueOnError = true;
84 private boolean logExceptions = true;
85
86 private final static String mtPrimary = "primary";
87 private final static String mtSuccess = "success";
88 private final static String mtInfo = "info";
89 private final static String mtWarning = "warning";
90 private final static String mtDanger = "danger";
91
92 public LibraryManagerImpl() {
93 libraryService = ClientFactory.getLibraryClient();
94 skipSections = setupSkipSections();
95 setupRecentTimerCheck();
96 lastUpdateTime = new Date().getTime();
97 continueOnError = ConfigManager.getBoolean("BuckoVidLib.continueOnError", true);
98 logExceptions = ConfigManager.getBoolean("BuckoVidLib.logExceptions", true);
99
100
101 }
102
103
104
105
106
107 @Override
108 public long getLastUpdateTime() {
109 return(this.lastUpdateTime);
110 }
111
112
113 @Override
114 public List<LibrarySection> getSectionList() {
115 if (library == null)
116 loadLibrary();
117 synchronized (library) {
118 return(library.getLibrarySections());
119 }
120 }
121
122
123
124
125 @Override
126 public List<LibrarySection> getSectionList(boolean allowRestricted) {
127 if (library == null)
128 loadLibrary();
129 synchronized (library) {
130 if (allowRestricted)
131 return(library.getLibrarySections());
132 return(library.getUnrestrictedLibrarySections());
133 }
134 }
135
136
137
138
139 @Override
140 public VideoBase getRandomMovie(boolean allowRestricted) {
141 if (library == null)
142 loadLibrary();
143
144 int total = 0;
145 VideoBase choice = null;
146 synchronized (library) {
147 List<LibrarySection> list = getSectionList(allowRestricted);
148 for (LibrarySection ls : list)
149 total += ls.getVideos().size();
150 int target = (int)(double)(Math.random()*(double)total);
151 log.info("random " + target + " of " + total);
152 for (LibrarySection ls : list) {
153 if (ls.getVideos().size() > target) {
154
155 choice = ls.getVideos().get(target);
156 break;
157 }
158 target -= ls.getVideos().size();
159 }
160 }
161 if (choice == null) {
162 log.warn("Failed to pick a random movie");
163 return(null);
164 }
165 return(choice);
166 }
167
168
169
170
171
172 @Override
173 public Video getVideoFromKey(int key, boolean allowRestricted) {
174 if (library == null)
175 loadLibrary();
176 log.info("getVideoFromKey: " + DomainConverter.intToKey(key) + " allowRestrict?: " + allowRestricted);
177 VideoBase vb = library.getVideoBase(key);
178 if (vb == null)
179 return(null);
180 Video v = db.getVideo(vb.getId());
181 if (v == null)
182 return(null);
183
184
185
186
187
188 v.setVideoTexts(db.getVideoTexts(v.getId()));
189 if (v instanceof TVShow) {
190 TVShow tv = (TVShow)v;
191 tv.setSeasons(db.getTVSeasons(v.getId()));
192 }
193 if (allowRestricted)
194 return(v);
195 if (isRestricted(v))
196 return(null);
197 return(v);
198 }
199
200 private boolean isRestricted(VideoBase v) {
201 for (LibrarySection ls : library.getUnrestrictedLibrarySections()) {
202 if (ls.getVideos().contains(v))
203 return(false);
204 }
205 return(true);
206 }
207
208
209
210
211 @Override
212 public VideoBase getVideoBaseFromKey(int key, boolean allowRestricted) {
213 if (library == null)
214 loadLibrary();
215 log.debug("getVideoBaseFromKey: " + DomainConverter.intToKey(key) + " AR?: " + allowRestricted);
216 VideoBase v = library.getVideoBase(key);
217 if (allowRestricted)
218 return(v);
219 if (isRestricted(v))
220 return(null);
221 return(v);
222 }
223
224 @Override
225 public List<VideoBase> getNewestVideos(int count, boolean allowRestricted) {
226 if (library == null)
227 loadLibrary();
228 List<VideoBase> vl;
229 List<VideoBase> vla = new ArrayList<VideoBase>();
230 synchronized (library) {
231 vl = library.getNewestVideos();
232 for (VideoBase v : vl) {
233 if (allowRestricted || !isRestricted(v)) {
234 vla.add(v);
235 if (vla.size() >= count)
236 return(vla);
237 }
238 }
239 }
240 return(vla);
241 }
242
243
244
245
246
247 @Override
248 public List<VideoBase> findVideos(Pattern pattern, boolean allowRestricted, int maxReturned) {
249 if (library == null)
250 loadLibrary();
251 List<VideoBase> vbl = new ArrayList<VideoBase>();
252 synchronized (library) {
253 for (VideoBase v : library.getVideoMap().values()) {
254 if (allowRestricted || !isRestricted(v)) {
255
256 if (!pattern.matcher(v.getTitle()).matches())
257 continue;
258 vbl.add(v);
259 if (vbl.size() >= maxReturned)
260 break;
261 }
262 }
263 }
264 Collections.sort(vbl, videoBaseNameSort);
265 return(vbl);
266 }
267 private class VideoBaseNameSort implements Comparator<VideoBase> {
268 @Override
269 public int compare(VideoBase arg0, VideoBase arg1) {
270 return(arg0.getTitle().compareToIgnoreCase(arg1.getTitle()));
271 }
272 }
273 private VideoBaseNameSort videoBaseNameSort = new VideoBaseNameSort();
274
275
276
277
278
279 @Override
280 public List<Actor> findActors(String key, int maxReturned) {
281 if (library == null)
282 loadLibrary();
283 List<Actor> la = db.findActors(key, maxReturned);
284 Collections.sort(la, actorNameSort);
285 return(la);
286 }
287 private class ActorNameSort implements Comparator<Actor> {
288 @Override
289 public int compare(Actor arg0, Actor arg1) {
290 return(arg0.getName().compareToIgnoreCase(arg1.getName()));
291 }
292 }
293 private ActorNameSort actorNameSort = new ActorNameSort();
294
295
296
297
298
299 @Override
300 public List<Director> findDirectors(String key, int maxReturned) {
301 List<Director> ld = db.findDirectors(key, maxReturned);
302 Collections.sort(ld, directorNameSort);
303 return(ld);
304 }
305
306 private class DirectorNameSort implements Comparator<Director> {
307 @Override
308 public int compare(Director arg0, Director arg1) {
309 return(arg0.getName().compareToIgnoreCase(arg1.getName()));
310 }
311 }
312 private DirectorNameSort directorNameSort = new DirectorNameSort();
313
314
315
316
317
318 @Override
319 public List<Writer> findWriters(String key, int maxReturned) {
320 List<Writer> lw = db.findWriters(key, maxReturned);
321 Collections.sort(lw, writerNameSort);
322 return(lw);
323 }
324 private class WriterNameSort implements Comparator<Writer> {
325 @Override
326 public int compare(Writer arg0, Writer arg1) {
327 return(arg0.getName().compareToIgnoreCase(arg1.getName()));
328 }
329 }
330 private WriterNameSort writerNameSort = new WriterNameSort();
331
332
333 @Override
334 public List<VideoBase> getLibrarySectionVideos(int key) {
335 if (library == null)
336 loadLibrary();
337 List<VideoBase> vl = new ArrayList<VideoBase>();
338 synchronized (library) {
339 LibrarySection ls = library.getLibrarySection(key);
340 for (VideoBase v : ls.getVideos()) {
341 vl.add(v);
342 }
343 }
344 return(vl);
345
346 }
347
348
349
350
351 @Override
352 public List<VideoBase> getDirectorVideos(int key) {
353 if (library == null)
354 loadLibrary();
355 List<VideoBase> vl = new ArrayList<VideoBase>();
356 List<Integer> videoIds = db.getVideoIdsByDirector(key);
357 synchronized (library) {
358 for (int videoId : videoIds) {
359 vl.add(library.getVideoById(videoId));
360 }
361 }
362 return vl;
363 }
364
365
366
367
368 @Override
369 public List<VideoBase> getWriterVideos(int key) {
370 if (library == null)
371 loadLibrary();
372 List<VideoBase> vl = new ArrayList<VideoBase>();
373 synchronized (library) {
374 List<Integer> videoIds = db.getVideoIdsByWriter(key);
375 for (int videoId : videoIds) {
376 vl.add(library.getVideoById(videoId));
377 }
378 }
379 return vl;
380 }
381
382
383
384
385 @Override
386 public List<VideoBase> getActorVideos(int key) {
387 if (library == null)
388 loadLibrary();
389 List<VideoBase> vl = new ArrayList<VideoBase>();
390 List<Integer> videoIds = db.getVideoIdsByActor(key);
391 synchronized (library) {
392 for (int videoId : videoIds) {
393 vl.add(library.getVideoById(videoId));
394 }
395 }
396 return vl;
397 }
398
399
400
401
402 @Override
403 public TVSeason getTVSeasonFromHashKey(int key) {
404 return(db.getTVSeasonFromHashKey(key));
405 }
406
407
408 @Override
409 public int getMovieCount(boolean allowRestricted) {
410 if (library == null)
411 loadLibrary();
412 int count = 0;
413 synchronized (library) {
414 for (LibrarySection ls : this.getSectionList(allowRestricted)) {
415 if (ls.getType() == Type.Movie)
416 count += ls.getVideoCount();
417 }
418 }
419 return(count);
420
421 }
422
423
424
425
426 @Override
427 public int getTVShowCount(boolean allowRestricted) {
428 if (library == null)
429 loadLibrary();
430 int count = 0;
431 synchronized (library) {
432 for (LibrarySection ls : this.getSectionList(allowRestricted)) {
433 if (ls.getType() == Type.TVShow)
434 count += ls.getVideoCount();
435 }
436 }
437 return(count);
438 }
439
440 @Override
441
442
443 public int getTVEpisodeCount(boolean allowRestricted) {
444 if (library == null)
445 loadLibrary();
446 int count = 0;
447 List<TVSeason> seasons = db.getTVSeasons();
448 for (TVSeason season : seasons) {
449 count += season.getEpisodeCount();
450 }
451 return(count);
452 }
453
454
455
456 private void loadLibrary() {
457 log.info("Loading library");
458 boolean b = ConfigManager.getBoolean("BuckoVidLib.runFromPlex", true);
459 if (b)
460 loadLibraryFromPlex();
461 else
462 loadLibraryFromDatabase();
463 log.info("library loaded");
464 }
465
466
467 private void loadLibraryFromDatabase() {
468 log.info("Loading library from Database");
469 library = new Library();
470 List<String> restrictedSectionNames = setupRestrictedSectionNames();
471 library.setRestrictedSectionNames(restrictedSectionNames);
472 List<VideoBase> videos = db.getVideoBases();
473 synchronized (library) {
474 List<LibrarySection> list = db.getLibrarySections();
475 log.info("Loading " + list.size() + " sections with " + videos.size() + " videos.");
476 for (LibrarySection ls : list) {
477 if (restrictedSectionNames.contains(ls.getName()))
478 ls.setRestricted(true);
479 }
480 for (VideoBase v : videos) {
481 for (LibrarySection ls : list) {
482 if (v.getSection() == ls.getKey()) {
483 library.addVideo(v.getHashKey(), v);
484 ls.addVideo(v);
485 break;
486 }
487 }
488 }
489 for (LibrarySection ls : list) {
490 Collections.sort(ls.getVideos(), videoBaseIndexSort);
491 }
492 library.setLibrarySections(list);
493 if (log.isInfoEnabled()) {
494 if (library.getNewestVideo() == null) {
495 log.warn("Empty library!");
496 } else {
497 log.info("newest video: " + library.getNewestVideo().getTitle()
498 + " (" + library.getNewestVideo().getYear()
499 + ") h:" + DomainConverter.intToKey(library.getNewestVideo().getHashKey())
500 + " d:" + library.getNewestVideo().getAddedAt());
501 }
502 }
503 }
504 }
505
506
507 private void loadLibraryFromPlex() {
508 log.info("Loading library from plex");
509 library = new Library();
510 List<String> restrictedSectionNames = setupRestrictedSectionNames();
511 library.setRestrictedSectionNames(restrictedSectionNames);
512 db.truncateLibrary();
513 synchronized (library) {
514 MediaContainer mc = libraryService.sections();
515 List<LibrarySection> list = new ArrayList<LibrarySection>();
516 for (Directory d : mc.getDirectories()) {
517 if (!skipSections.contains(d.getTitle()))
518 list.add(DomainConverter.toLibrarySection(d));
519 }
520 for (LibrarySection ls : list) {
521 if (restrictedSectionNames.contains(ls.getName()))
522 ls.setRestricted(true);
523 db.addLibrarySection(ls);
524 log.info("Processing section " + ls.getName());
525 mc = libraryService.videosAll(ls.getKey());
526 if (ls.getType() == Type.Movie) {
527 for (tv.plex.domain.Video pv : mc.getVideos()) {
528 com.buckosoft.BuckoVidLib.domain.Video v = DomainConverter.toVideo(pv);
529 v.setSection(ls.getKey());
530 v.setHashKey(v.hashCode());
531 updateMappings(null, library, v);
532 try {
533 db.saveVideo(v);
534 } catch (Exception e) {
535 throw new RuntimeException("Failed to add video: " + v.getTitle() + " (" + v.getYear() + ") hash: " + v.getHashKey(), e);
536 }
537 library.addVideo(v.getHashKey(), v);
538 ls.addVideo(v);
539 }
540 } else if (ls.getType() == Type.TVShow) {
541 for (tv.plex.domain.Directory pd : mc.getDirectories()) {
542 com.buckosoft.BuckoVidLib.domain.TVShow tv = DomainConverter.toTVShow(pd);
543 tv.setSection(ls.getKey());
544 tv.setHashKey(tv.hashCode());
545 updateMappings(null, library, tv);
546 try {
547 db.saveVideo(tv);
548 } catch (Exception e) {
549 throw new RuntimeException("Failed to add TV Show: " + tv.getTitle() + " (" + tv.getYear() + ") hash: " + tv.getHashKey(), e);
550 }
551 loadTVSeasonsFromPlex(tv);
552 library.addVideo(tv.getHashKey(), tv);
553 ls.addVideo(tv);
554 }
555 }
556 }
557 library.setLibrarySections(list);
558 }
559 if (log.isInfoEnabled()) {
560 log.info("newest video: " + library.getNewestVideo().getTitle()
561 + " (" + library.getNewestVideo().getYear()
562 + ") h:" + DomainConverter.intToKey(library.getNewestVideo().getHashKey())
563 + " d:" + library.getNewestVideo().getAddedAt());
564 }
565 }
566
567 PlexLibraryUpdater plexLibraryUpdaterTask;
568 Thread plexLibraryUpdaterThread;
569 @Override
570 public void startRefreshLibraryFromPlex() {
571 plexLibraryUpdaterTask = new PlexLibraryUpdater();
572 plexLibraryUpdaterThread = new Thread(plexLibraryUpdaterTask);
573 plexLibraryUpdaterThread.setName("PlexLibraryRefresh");
574 plexLibraryUpdaterThread.start();
575
576 }
577 @Override
578 public RestRefreshStatus getRefreshLibraryFromPlexStatus() {
579 if (plexLibraryUpdaterTask == null) {
580 RestRefreshStatus rls = new RestRefreshStatus();
581 rls.addMessage("warning", "Library Updater is null");
582 return(rls);
583 }
584 return(plexLibraryUpdaterTask.getRefreshStatus());
585 }
586
587
588
589 public class PlexLibraryUpdater implements Runnable {
590 private final Log log = LogFactory.getLog(getClass());
591 private RestRefreshStatus restRefreshStatus = new RestRefreshStatus();
592
593
594
595
596
597 public RestRefreshStatus getRefreshStatus() {
598 RestRefreshStatus rls = new RestRefreshStatus();
599 List<RestStatusString> messages = new ArrayList<RestStatusString>();
600 synchronized (restRefreshStatus) {
601 rls.setPercentComplete(restRefreshStatus.getPercentComplete());
602 if (restRefreshStatus.getMessages() != null)
603 for (RestStatusString rss : restRefreshStatus.getMessages())
604 messages.add(new RestStatusString(rss.getType(), rss.getMessage()));
605 }
606 rls.setMessages(messages);
607 if (log.isTraceEnabled()) {
608 for (RestStatusString rss : rls.getMessages())
609 log.trace("RestStatusString: " + rss.getType() + " " + rss.getMessage());
610 }
611 return(rls);
612 }
613
614 private void addMessage(String type, String message) {
615 log.info(message);
616 synchronized(restRefreshStatus) {
617 restRefreshStatus.addMessage(type, message);
618 }
619 }
620
621 private void abort(String message) {
622 addMessage(mtDanger, "Update aborted: " + message);
623 throw new RuntimeException(message);
624 }
625
626
627
628
629
630 private String statusMessage(String s, VideoBase v, String message) {
631 StringBuilder sb = new StringBuilder();
632 if (s == null) {
633 sb.append(v.getTitle());
634 sb.append(" (");
635 sb.append(v.getYear());
636 sb.append(") : ");
637 } else {
638 sb.append(s);
639 sb.append(" : ");
640 }
641 sb.append(message);
642 return(sb.toString());
643 }
644
645
646
647 @Override
648 public void run() {
649
650 addMessage(mtPrimary, "Updating library from plex...");
651 boolean reloadme = false;
652
653 Library plibrary = new Library();
654 LibraryService libraryService;
655 libraryService = ClientFactory.getLibraryClient();
656 List<String> restrictedSectionNames = setupRestrictedSectionNames();
657 List<String> skipSections = setupSkipSections();
658 List<Integer> existingHashKeys = new ArrayList<Integer>();
659 for (Integer hk : library.getVideoMap().keySet())
660 existingHashKeys.add(hk);
661 MediaContainer mc = libraryService.sections();
662 List<LibrarySection> list = new ArrayList<LibrarySection>();
663 for (Directory d : mc.getDirectories()) {
664 if (!skipSections.contains(d.getTitle()))
665 list.add(DomainConverter.toLibrarySection(d));
666 }
667 addMessage(mtInfo, "Servicing " + list.size() + " sections");
668 int currentLibrarySectionIndex = -1;
669 for (LibrarySection ls : list) {
670
671 currentLibrarySectionIndex++;
672 int pcBase = currentLibrarySectionIndex * 100 / list.size();
673 double pcInc = 100 / list.size();
674 synchronized(restRefreshStatus) {
675 restRefreshStatus.setPercentComplete(pcBase);
676 }
677 boolean sortme = false;
678
679 if (restrictedSectionNames.contains(ls.getName()))
680 ls.setRestricted(true);
681 if (library.getLibrarySection(ls.getKey()) == null) {
682 LibrarySection nls = new LibrarySection(ls);
683 addMessage(mtInfo, "Adding new" + (ls.isRestricted() ? " restricted" : "") + " LibrarySection: " + ls.getName());
684 db.addLibrarySection(ls);
685 library.addLibrarySection(nls);
686 }
687 log.info("Processing section " + ls.getName());
688 mc = libraryService.videosAll(ls.getKey());
689 VideoBase ovb;
690 Video ov;
691 String s;
692 if (ls.getType() == Type.Movie) {
693 int pcIndex = -1;
694 for (tv.plex.domain.Video pv : mc.getVideos()) {
695 pcIndex++;
696 int i = (int)(pcIndex*pcInc/mc.getSize());
697 synchronized(restRefreshStatus) {
698 restRefreshStatus.setPercentComplete(pcBase+i);
699 }
700
701
702 if (pv.getTitle().equals(("The Boxtrolls"))) {
703 log.trace("Doing The Boxtrolls...");
704 }
705 Video v = DomainConverter.toVideo(pv);
706 v.setSection(ls.getKey());
707 v.setHashKey(v.hashCode());
708 existingHashKeys.remove((Integer)v.getHashKey());
709 updateMappings(restRefreshStatus, library, v);
710 synchronized (library) {
711 ovb = library.getVideoBase(v.getHashKey());
712 }
713 if (ovb == null) {
714 s = statusMessage(null, v, "New Video");
715 log.info(s);
716 try {
717 db.saveVideo(v);
718 synchronized (library) {
719 library.addVideo(v.getHashKey(), v);
720 library.getLibrarySection(ls.getKey()).addVideo(v);
721 }
722 plibrary.addVideo(v.getHashKey(), v);
723 ls.addVideo(v);
724 sortme = true;
725 addMessage(mtSuccess, s);
726 } catch (Exception e) {
727 s = statusMessage(s, v, "Failed to save video");
728 addMessage(mtDanger, s);
729 if (!continueOnError)
730 throw new RuntimeException(s, e);
731 }
732 } else {
733 ov = db.getVideo(ovb.getId());
734 if (ov == null) {
735 addMessage(mtDanger, " : failed to load Video " + ovb.getId() + " from database");
736 } else {
737 ProcessStatus ps = processVideo(v, ov);
738 if (ps.reloadme)
739 reloadme = true;
740 if (ps.sortme)
741 sortme = true;
742 ls.addVideo(ov);
743 plibrary.addVideo(v.getHashKey(), ov);
744 }
745 }
746 v.setVideoTexts(null);
747 }
748 } else if (ls.getType() == Type.TVShow) {
749 int pcIndex = -1;
750 TVShow otv;
751 Collection<Directory> dirs = mc.getDirectories();
752 for (tv.plex.domain.Directory pd : dirs) {
753 pcIndex++;
754 int i = (int)(pcIndex*pcInc/mc.getSize());
755 log.trace("i=" + i);
756 synchronized(restRefreshStatus) {
757 restRefreshStatus.setPercentComplete(pcBase+i);
758 }
759 TVShow tv = DomainConverter.toTVShow(pd);
760 tv.setSection(ls.getKey());
761 tv.setHashKey(tv.hashCode());
762 existingHashKeys.remove((Integer)tv.getHashKey());
763 updateMappings(restRefreshStatus, library, tv);
764 synchronized (library) {
765 ovb = library.getVideoBase(tv.getHashKey());
766 }
767 if (ovb == null) {
768 s = statusMessage(null, tv, "New TV Show");
769 log.info(s);
770 try {
771 db.saveVideo(tv);
772 synchronized (library) {
773 library.addVideo(tv.getHashKey(), tv);
774 library.getLibrarySection(ls.getKey()).addVideo(tv);
775 }
776 plibrary.addVideo(tv.getHashKey(), tv);
777 ls.addVideo(tv);
778 sortme = true;
779 addMessage(mtSuccess, s);
780 } catch (Exception e) {
781 s = statusMessage(s, tv, "Failed to save TVShow");
782 addMessage(mtDanger, s);
783 if (!continueOnError)
784 throw new RuntimeException(s, e);
785 break;
786 }
787 otv = null;
788 } else {
789 otv = (TVShow)db.getVideo(ovb.getId());
790 if (otv == null) {
791 addMessage(mtDanger, " : failed to load TVShow " + ovb.getId() + " from database");
792 } else {
793 ProcessStatus ps = processVideo(tv, otv);
794 tv.setId(otv.getId());
795 if (ps.reloadme)
796 reloadme = true;
797 if (ps.sortme)
798 sortme = true;
799 ls.addVideo(otv);
800 plibrary.addVideo(tv.getHashKey(), tv);
801 }
802 }
803 tv.setVideoTexts(null);
804 loadTVSeasonsFromPlex(tv);
805 }
806 } else {
807 abort("Bug: Unknown section type in " + ls.getName());
808 }
809 if (sortme) {
810 reloadme = true;
811 Collections.sort(ls.getVideos(), videoNameSort);
812 int i =0;
813 for (VideoBase vb : ls.getVideos()) {
814 Video v = (Video)vb;
815 i++;
816 v.setSortIndex(i);
817 db.saveVideo(v);
818 }
819 }
820 }
821 plibrary.setLibrarySections(list);
822
823 if (!existingHashKeys.isEmpty()) {
824 log.info("Deleting " + existingHashKeys.size() + " videos");
825
826 reloadme = true;
827 for (Integer hk : existingHashKeys) {
828 VideoBase vb;
829 synchronized (library) {
830 vb = library.getVideoBase(hk);
831 }
832 if (vb == null) {
833 abort("Wanted to delete Video for key " + hk + " but it doesn't exist");
834 }
835 addMessage(mtWarning, statusMessage(null, vb, "Deleted"));
836 db.deleteVideo(vb);
837 }
838 }
839 checkImageCache(plibrary);
840 if (reloadme) {
841 synchronized(restRefreshStatus) {
842 restRefreshStatus.setPercentComplete(99);
843 }
844 addMessage(mtInfo, "Reloading library");
845 loadLibraryFromDatabase();
846 lastUpdateTime = new Date().getTime();
847 }
848 synchronized(restRefreshStatus) {
849 restRefreshStatus.setPercentComplete(100);
850 }
851 addMessage(mtSuccess, "Done.");
852 if (log.isInfoEnabled()) {
853 log.info("newest video: " + plibrary.getNewestVideo().getTitle()
854 + " (" + plibrary.getNewestVideo().getYear()
855 + ") d:" + plibrary.getNewestVideo().getAddedAt());
856 }
857 }
858
859 private class ProcessStatus {
860 boolean sortme = false;
861 boolean reloadme = false;
862 }
863 private ProcessStatus processVideo(Video v, Video ov) {
864 ProcessStatus ps = new ProcessStatus();
865 boolean saveme = false;
866 boolean statusWarning = false;
867 String s;
868 s = null;
869 if (ov.getUpdatedAt() != v.getUpdatedAt()) {
870 ov.setUpdatedAt(v.getUpdatedAt());
871 saveme = true;
872 s = statusMessage(s, v, "timekey updated");
873 }
874
875 VideoTexts vt = db.getVideoTexts(ov.getId());
876 if (vt == null) {
877 vt = new VideoTexts();
878 vt.setVideoId(ov.getId());
879 }
880 boolean vtupdate = false;
881 if (v.getVideoTexts() == null) {
882 if (vt.getTagline() != null) {
883 s = statusMessage(s, v, "tagline disappeared");
884 vt.setTagline(null);
885 vtupdate = true;
886 }
887 if (vt.getSummary() != null) {
888 s = statusMessage(s, v, "summary disappeared");
889 vt.setSummary(null);
890 vtupdate = true;
891 }
892 } else {
893 VideoTexts vvt = v.getVideoTexts();
894 if (vvt.getSummary() != null && !vvt.getSummary().equals(vt.getSummary())) {
895 s = statusMessage(s, v, "summary updated");
896 vt.setSummary(vvt.getSummary());
897 vtupdate = true;
898 try {
899 if (URLEncoder.encode(vt.getSummary(), "utf-8").length() > db.getMaxVideoTextsSummaryLength()) {
900 statusWarning = true;
901 s = statusMessage(s, v, "summary too big (" + vt.getSummary().length() + ")");
902 }
903 } catch (UnsupportedEncodingException e) {
904 statusWarning = true;
905 s = statusMessage(s, v, "summary encoding failure");
906 }
907 }
908 if (vvt.getTagline() != null && !vvt.getTagline().equals(vt.getTagline())) {
909 s = statusMessage(s, v, "tagline updated");
910 vt.setTagline(v.getVideoTexts().getTagline());
911 vtupdate = true;
912 try {
913 if (URLEncoder.encode(vt.getTagline(), "utf-8").length() > db.getMaxVideoTextsTaglineLength()) {
914 statusWarning = true;
915 s = statusMessage(s, v, "tagline too big (" + vt.getTagline().length() + ")");
916 }
917 } catch (UnsupportedEncodingException e) {
918 statusWarning = true;
919 s = statusMessage(s, v, "tagline encoding failure");
920 }
921 }
922 }
923 if (v.getSortTitle() != null && !v.getSortTitle().equals(ov.getSortTitle())) {
924 ov.setSortTitle(v.getSortTitle());
925 s = statusMessage(s, v, "sortTitle updated");
926 saveme = true;
927 ps.sortme = true;
928 } else if (ov.getSortTitle() != null && v.getSortTitle() == null) {
929 ov.setSortTitle(v.getSortTitle());
930 s = statusMessage(s, v, "sortTitle updated");
931 saveme = true;
932 ps.sortme = true;
933 }
934 if (v.getStudio() != null && !v.getStudio().equals(ov.getStudio())) {
935 ov.setStudio(v.getStudio());
936 s = statusMessage(s, v, "studio updated");
937 saveme = true;
938 } else if (ov.getStudio() != null && v.getStudio() == null) {
939 ov.setStudio(v.getStudio());
940 s = statusMessage(s, v, "studio updated");
941 saveme = true;
942 }
943 if (saveme || vtupdate) {
944 if (statusWarning) {
945 log.warn(s);
946 addMessage(mtDanger, s);
947 } else {
948 log.info(s);
949 addMessage(mtSuccess, s);
950 }
951 if (vtupdate) {
952 ov.setVideoTexts(vt);
953 saveme = true;
954 }
955 if (saveme) {
956 db.saveVideo(ov);
957 ps.reloadme = true;
958 }
959 }
960 ov.setVideoTexts(null);
961 return(ps);
962 }
963
964 private class CacheDirs {
965 File smPosterDir;
966 File lgPosterDir;
967 File rawPosterDir;
968 File artDir;
969 int count = 0;
970 }
971
972 private void checkImageCache(Library plibrary) {
973 CacheDirs cacheDirs = new CacheDirs();
974 boolean b = ConfigManager.getBoolean("BuckoVidLib.useImageCache", false);
975 if (!b)
976 return;
977 addMessage(mtInfo, "Refreshing images");
978 String base = ConfigManager.getString("BuckoVidLib.imageCacheBaseDir", null);
979 if (base == null) {
980 addMessage(mtDanger, "BuckoVidLib.imageCacheBaseDir is undefined");
981 return;
982 }
983 File dir = new File(base);
984 if (!dir.isDirectory()) {
985 addMessage(mtDanger, "BuckoVidLib.imageCacheBaseDir '" + base + "' is not a directory");
986 return;
987 }
988 cacheDirs.smPosterDir = new File(dir, "smposter");
989 cacheDirs.lgPosterDir = new File(dir, "lgposter");
990 cacheDirs.rawPosterDir = new File(dir, "rposter");
991 cacheDirs.artDir = new File(dir, "art");
992 if (!cacheDirs.smPosterDir.isDirectory()) {
993 if (!cacheDirs.smPosterDir.mkdir()) {
994 addMessage(mtDanger, "imageCache: failed to create smPosterDir: '" + cacheDirs.smPosterDir.getAbsolutePath() + "'");
995 return;
996 }
997 addMessage(mtInfo, "Created directory " + cacheDirs.smPosterDir.getAbsolutePath());
998 }
999 if (!cacheDirs.lgPosterDir.isDirectory()) {
1000 if (!cacheDirs.lgPosterDir.mkdir()) {
1001 addMessage(mtDanger, "imageCache: failed to create lgPosterDir: '" + cacheDirs.lgPosterDir.getAbsolutePath() + "'");
1002 return;
1003 }
1004 addMessage(mtInfo, "Created directory " + cacheDirs.lgPosterDir.getAbsolutePath());
1005 }
1006 if (!cacheDirs.rawPosterDir.isDirectory()) {
1007 if (!cacheDirs.rawPosterDir.mkdir()) {
1008 addMessage(mtDanger, "imageCache: failed to create smPosterDir: '" + cacheDirs.rawPosterDir.getAbsolutePath() + "'");
1009 return;
1010 }
1011 addMessage(mtInfo, "Created directory " + cacheDirs.rawPosterDir.getAbsolutePath());
1012 }
1013 if (!cacheDirs.artDir.isDirectory()) {
1014 if (!cacheDirs.artDir.mkdir()) {
1015 addMessage(mtDanger, "imageCache: failed to create smPosterDir: '" + cacheDirs.artDir.getAbsolutePath() + "'");
1016 return;
1017 }
1018 addMessage(mtInfo, "Created directory " + cacheDirs.artDir.getAbsolutePath());
1019 File in = new File(dir, "defaultBackground.jpg");
1020 File out = new File(cacheDirs.artDir, "0");
1021 try {
1022 FileUtils.copyFile(in, out);
1023 } catch (IOException e) {
1024 addMessage(mtWarning, "imageCache: failed to copy defaultBackground: '" + cacheDirs.artDir.getAbsolutePath() + "'");
1025 }
1026 in = new File(dir, "greyBackground.png");
1027 out = new File(cacheDirs.artDir, "1");
1028 try {
1029 FileUtils.copyFile(in, out);
1030 } catch (IOException e) {
1031 addMessage(mtWarning, "imageCache: failed to copy greyBackground: '" + cacheDirs.artDir.getAbsolutePath() + "'");
1032 }
1033 }
1034 restRefreshStatus.setPercentComplete(0);
1035 imageController.setUseImageCache(false);
1036 Collection<VideoBase> col = plibrary.getVideoMap().values();
1037 int todo = col.size();
1038 int i = 0;
1039
1040 for (VideoBase vb : col) {
1041 Video v = (Video)vb;
1042 restRefreshStatus.setPercentComplete(i++ * 100 / todo);
1043 String key = DomainConverter.intToKey(v.getHashKey());
1044 if (v.getTitle().equals(("North and South"))) {
1045 log.trace("Doing North and South...");
1046 }
1047
1048 try {
1049 processPoster(cacheDirs, v, key);
1050 } catch (Exception e) {
1051 String s = "Failed to read poster for " + v.getTitle();
1052 addMessage(mtDanger, s);
1053 if (logExceptions)
1054 log.warn(s, e);
1055 }
1056
1057 if (!processBackground(cacheDirs, v, key) && !continueOnError)
1058 break;
1059 if (v instanceof TVShow) {
1060 TVShow tv = (TVShow)v;
1061 log.info("Do TVShow " + tv.getTitle());
1062 for (TVSeason season : tv.getSeasons()) {
1063 try {
1064 processPoster(cacheDirs, v, DomainConverter.intToKey(season.getHashKey()));
1065 } catch (Exception e) {
1066 String s = "Failed to read poster for " + v.getTitle() + " : " + season.getTitle();
1067 addMessage(mtDanger, s);
1068 if (logExceptions)
1069 log.warn(s, e);
1070 }
1071 }
1072 }
1073 }
1074 addMessage(mtInfo, "Processed " + cacheDirs.count + " video images");
1075 restRefreshStatus.setPercentComplete(100);
1076 imageController.setUseImageCache(true);
1077 }
1078
1079 private void processPoster(CacheDirs cacheDirs, Video v, String key) throws Exception {
1080 File file = new File(cacheDirs.smPosterDir, key);
1081 if (file.isFile() && file.lastModified()/1000 > v.getUpdatedAt()) {
1082 long last = file.lastModified()/1000;
1083 long vlast = v.getUpdatedAt();
1084 log.debug("skipping because newer " + v.getTitle() + " (" + (vlast - last) + ")");
1085 return;
1086 }
1087 cacheDirs.count++;
1088 FileOutputStream fos = null;
1089 try {
1090 fos = new FileOutputStream(file);
1091 } catch (FileNotFoundException e) {
1092 String s = "posterCacheSM: Can't open '" + file.getAbsolutePath() + "' for writing";
1093 addMessage(mtDanger, s);
1094 throw(new Exception(s, e));
1095 }
1096
1097 try {
1098 if (fos != null)
1099 posterController.handleRequest(key, 150, 225, null, fos);
1100 } catch (Exception e) {
1101 String s = "posterCacheSM: Can't write '" + file.getAbsolutePath() + "'";
1102 addMessage(mtDanger, s);
1103 try { fos.close(); } catch (IOException e1) {}
1104 file.delete();
1105 throw(new Exception(s, e));
1106 }
1107 try {
1108 if (fos != null)
1109 fos.close();
1110 } catch (IOException e) {
1111 String s = "posterCacheSM: Can't write/close '" + file.getAbsolutePath() + "'";
1112 addMessage(mtDanger, s);
1113 file.delete();
1114 throw(new Exception(s, e));
1115 }
1116
1117 file = new File(cacheDirs.lgPosterDir, key);
1118 fos = null;
1119 try {
1120 fos = new FileOutputStream(file);
1121 } catch (FileNotFoundException e) {
1122 String s = "posterCacheLG: Can't open '" + file.getAbsolutePath() + "' for writing";
1123 addMessage(mtDanger, s);
1124 throw(new Exception(s, e));
1125 }
1126
1127 try {
1128 if (fos != null)
1129 posterController.handleRequest(key, 250, 375, null, fos);
1130 } catch (Exception e) {
1131 String s = "posterCacheLG: Can't write '" + file.getAbsolutePath() + "'";
1132 addMessage(mtDanger, s);
1133 try { fos.close(); } catch (IOException e1) {}
1134 file.delete();
1135 throw(new Exception(s, e));
1136 }
1137 try {
1138 if (fos != null)
1139 fos.close();
1140 } catch (IOException e) {
1141 String s = "posterCacheLG: Can't write/close '" + file.getAbsolutePath() + "'";
1142 addMessage(mtDanger, s);
1143 file.delete();
1144 throw(new Exception(s, e));
1145 }
1146
1147 file = new File(cacheDirs.rawPosterDir, key);
1148 fos = null;
1149 try {
1150 fos = new FileOutputStream(file);
1151 } catch (FileNotFoundException e) {
1152 String s = "posterCacheRaw: Can't open '" + file.getAbsolutePath() + "' for writing";
1153 addMessage(mtDanger, s);
1154 throw(new Exception(s, e));
1155 }
1156
1157 try {
1158 if (fos != null)
1159 posterController.handleRequest(key, 0, 0, null, fos);
1160 } catch (Exception e) {
1161 String s = "posterCacheRaw: Failure: " + e.getMessage();
1162 addMessage(mtDanger, s);
1163 try { fos.close(); } catch (IOException e1) {}
1164 file.delete();
1165 throw(new Exception(s, e));
1166 }
1167 try {
1168 if (fos != null)
1169 fos.close();
1170 } catch (IOException e) {
1171 String s = "posterCacheRaw: Can't write/close '" + file.getAbsolutePath() + "'";
1172 addMessage(mtDanger, s);
1173 file.delete();
1174 throw(new Exception(s, e));
1175 }
1176 }
1177
1178 private boolean processBackground(CacheDirs cacheDirs, Video v, String key) {
1179
1180 FileOutputStream fos = null;
1181 File file;
1182 file = new File(cacheDirs.artDir, key);
1183 fos = null;
1184 try {
1185 fos = new FileOutputStream(file);
1186 } catch (FileNotFoundException e) {
1187 addMessage(mtDanger, "imageCache: Can't open '" + file.getAbsolutePath() + "' for writing");
1188 if (!continueOnError)
1189 return(false);
1190 }
1191 try {
1192 if (fos != null)
1193 imageController.handleRequest(key, fos);
1194 } catch (Exception e) {
1195 addMessage(mtDanger, "imageCache: Failure: " + e.getMessage());
1196 try { fos.close(); } catch (IOException e1) {}
1197 file.delete();
1198 if (!continueOnError)
1199 return(false);
1200 }
1201 try {
1202 if (fos != null)
1203 fos.close();
1204 } catch (IOException e) {
1205 addMessage(mtDanger, "imageCache: Can't write/close '" + file.getAbsolutePath() + "'");
1206 if (!continueOnError)
1207 return(false);
1208 }
1209 return(true);
1210 }
1211 }
1212
1213 private class VideoNameSort implements Comparator<VideoBase> {
1214 @Override
1215 public int compare(VideoBase arg0, VideoBase arg1) {
1216 Video v0 = (Video)arg0;
1217 Video v1 = (Video)arg1;
1218 String s0;
1219 String s1;
1220 s0 = v0.getSortTitle();
1221 s1 = v1.getSortTitle();
1222 if (s0 == null)
1223 s0 = v0.getTitle();
1224 if (s1 == null)
1225 s1 = v1.getTitle();
1226
1227 return(s0.compareToIgnoreCase(s1));
1228 }
1229 }
1230 private VideoNameSort videoNameSort = new VideoNameSort();
1231
1232 private class VideoBaseIndexSort implements Comparator<VideoBase> {
1233 @Override
1234 public int compare(VideoBase arg0, VideoBase arg1) {
1235 if (arg0.getSortIndex() == arg1.getSortIndex())
1236 return(arg0.getTitle().compareToIgnoreCase(arg1.getTitle()));
1237 return(Integer.compare(arg0.getSortIndex(), arg1.getSortIndex()));
1238 }
1239 }
1240 private VideoBaseIndexSort videoBaseIndexSort = new VideoBaseIndexSort();
1241
1242 private SimpleDateFormat _df = new SimpleDateFormat("YYYYMMdd:HHmmss");
1243 private String debugCal(long cal) {
1244 Date d = new Date(cal*1000);
1245 return(_df.format(d));
1246 }
1247
1248 private void loadTVSeasonsFromPlex(TVShow tv) {
1249 if (tv.getTitle().equals(("North and South"))) {
1250 log.trace("Doing North and South...");
1251 }
1252 MediaContainer mc = libraryService.tvSeasons(tv.getPlexKey());
1253 log.info("loadTVSeasonsFromPlex " + tv.getTitle() + " :" + tv.getAddedAt() + " u:" + tv.getUpdatedAt());
1254 if (log.isDebugEnabled())
1255 log.debug("zz " + tv.getTitle() + " added:" + debugCal(tv.getAddedAt()) + " upd:" + debugCal(tv.getUpdatedAt()));
1256 for (tv.plex.domain.Directory pd : mc.getDirectories()) {
1257 if (pd.getKey().endsWith("/allLeaves"))
1258 continue;
1259 TVSeason season = DomainConverter.toTVSeason(pd);
1260 season.setVideoId(tv.getId());
1261 if (log.isDebugEnabled())
1262 log.debug("zz " + season.getTitle() + " added:" + debugCal(season.getAddedAt()) + " upd:" + debugCal(season.getUpdatedAt()));
1263 MediaContainer ms = libraryService.tvShows(season.getPlexKey());
1264 for (tv.plex.domain.Video s : ms.getVideos()) {
1265 if (s.getAddedAt() > season.getAddedAt())
1266 tv.setAddedAt(s.getAddedAt());
1267 }
1268 season.setHashKey(season.hashCode());
1269 TVSeason oseason = db.getTVSeasonFromHashKey(season.getHashKey());
1270 if (oseason == null) {
1271 db.saveTVSeason(season);
1272 } else {
1273 String s = null;
1274 if (oseason.getPlexKey() != season.getPlexKey())
1275 s = "plexKey Collision on " + tv.getTitle() + " : " + oseason.getTitle();
1276 if (oseason.getVideoId() != season.getVideoId())
1277 s = "Mismatched videoId on " + tv.getTitle() + " : " + oseason.getTitle();
1278 if (!oseason.getTitle().equals(season.getTitle()))
1279 s = "Mismatched title on " + tv.getTitle() + " : " + oseason.getTitle();
1280 if (s != null) {
1281 log.error(s);
1282 if (!continueOnError)
1283 throw new RuntimeException(s);
1284 break;
1285 }
1286 oseason.setEpisodeCount(season.getEpisodeCount());
1287 oseason.setUpdatedAt(season.getUpdatedAt());
1288 db.saveTVSeason(oseason);
1289 }
1290 tv.addSeason(season);
1291 }
1292 }
1293
1294 private void updateMappings(RestRefreshStatus restRefreshStatus, Library library, Video video) {
1295 Set<Genre> genres = library.getGenres();
1296 for (Genre g : video.getGenres()) {
1297 for (Genre gs : genres) {
1298 if (g.getTag().equals(gs.getTag())) {
1299 g.setId(gs.getId());
1300 break;
1301 }
1302 }
1303 if (g.getId() != 0)
1304 continue;
1305 Genre gs = db.getGenre(g.getTag());
1306 if (gs != null) {
1307 g.clone(gs);
1308 genres.add(g);
1309 continue;
1310 }
1311 genres.add(g);
1312 if (restRefreshStatus != null)
1313 synchronized (restRefreshStatus) {
1314 restRefreshStatus.addMessage(mtInfo, "Adding Genre: " + g.getTag());
1315 }
1316 db.addGenre(g);
1317 }
1318 Set<Actor> actors = library.getActors();
1319 for (Actor a : video.getActors()) {
1320 for (Actor as : actors) {
1321 if (a.getName().equals(as.getName())) {
1322 a.setId(as.getId());
1323 break;
1324 }
1325 }
1326 if (a.getId() != 0)
1327 continue;
1328 Actor as = db.getActor(a.getName());
1329 if (as != null) {
1330 a.clone(as);
1331 actors.add(a);
1332 continue;
1333 }
1334 actors.add(a);
1335 if (restRefreshStatus != null)
1336 synchronized (restRefreshStatus) {
1337 restRefreshStatus.addMessage(mtInfo, "Adding Actor: " + a.getName());
1338 }
1339 db.addActor(a);
1340 }
1341 Set<Director> directors = library.getDirectors();
1342 for (Director d : video.getDirectors()) {
1343 for (Director ds : directors) {
1344 if (d.getName().equals(ds.getName())) {
1345 d.setId(ds.getId());
1346 break;
1347 }
1348 }
1349 if (d.getId() != 0)
1350 continue;
1351 Director ds = db.getDirector(d.getName());
1352 if (ds != null) {
1353 d.clone(ds);
1354 directors.add(d);
1355 continue;
1356 }
1357 directors.add(d);
1358 if (restRefreshStatus != null)
1359 synchronized (restRefreshStatus) {
1360 restRefreshStatus.addMessage(mtInfo, "Adding Director: " + d.getName());
1361 }
1362 db.addDirector(d);
1363 }
1364 Set<Writer> writers = library.getWriters();
1365 for (Writer w : video.getWriters()) {
1366 for (Writer ws : writers) {
1367 if (w.getName().equals(ws.getName())) {
1368 w.setId(ws.getId());
1369 break;
1370 }
1371 }
1372 if (w.getId() != 0)
1373 continue;
1374 Writer ws = db.getWriter(w.getName());
1375 if (ws != null) {
1376 w.clone(ws);
1377 writers.add(w);
1378 continue;
1379 }
1380 writers.add(w);
1381 if (restRefreshStatus != null)
1382 synchronized (restRefreshStatus) {
1383 restRefreshStatus.addMessage(mtInfo, "Adding Writer: " + w.getName());
1384 }
1385 db.addWriter(w);
1386 }
1387 }
1388
1389 private List<String> setupSkipSections() {
1390 List<String> skipSections = new ArrayList<String>();
1391 String s = ConfigManager.getString("BuckoVidLib.skipSections", null);
1392 if (s != null) {
1393 String[] ss = s.split(",");
1394 for (String t : ss) {
1395 skipSections.add(t.trim());
1396 log.info("skipSections: " + t);
1397 }
1398 } else {
1399 log.info("skipSections: none");
1400 }
1401 return(skipSections);
1402 }
1403 private List<String> setupRestrictedSectionNames() {
1404 List<String> restrictedSections = new ArrayList<String>();
1405 String s = ConfigManager.getString("BuckoVidLib.restrictedSections", null);
1406 if (s != null) {
1407 String[] ss = s.split(",");
1408 for (String t : ss) {
1409 restrictedSections.add(t.trim());
1410 log.debug("restrictedSections: " + t);
1411 }
1412 }
1413 return(restrictedSections);
1414 }
1415
1416
1417 private Timer recentCheckTimer = new Timer("RecentCheck");
1418 private TimerTask recentCheckTask = new RecentCheckTask();
1419 private void setupRecentTimerCheck() {
1420 int delay = ConfigManager.getInt("BuckoVidLib.recentUpdateCheck", 60);
1421 recentCheckTimer.schedule(recentCheckTask, delay*1000, delay*1000);
1422 }
1423 private class RecentCheckTask extends TimerTask {
1424
1425 @Override
1426 public void run() {
1427 log.debug("Checking recently added:");
1428 if (library == null) {
1429 log.debug("library == null");
1430 loadLibrary();
1431 return;
1432 }
1433 int vc = db.getVideoCount();
1434 log.debug("video count: " + vc);
1435 MediaContainer mc = libraryService.recentlyAdded();
1436 if (mc == null)
1437 return;
1438 ArrayList<Video> list = new ArrayList<Video>();
1439 for (Directory d : mc.getDirectories()) {
1440 if (!skipSections.contains(d.getTitle())) {
1441 TVShow tv = DomainConverter.toTVShow(d);
1442 boolean match = false;
1443 for (Video v : list){
1444 if (v.hashCode() == tv.hashCode()) {
1445 match = true;
1446 break;
1447 }
1448 }
1449 if (!match)
1450 list.add(tv);
1451 }
1452 }
1453 for (tv.plex.domain.Video pv : mc.getVideos()) {
1454 Video bv = DomainConverter.toVideo(pv);
1455 boolean match = false;
1456 for (Video v : list){
1457 if (v.hashCode() == bv.hashCode()) {
1458 match = true;
1459 break;
1460 }
1461 }
1462 if (!match)
1463 list.add(bv);
1464 }
1465 Video newestVideo = null;
1466 for (Video v : list) {
1467 if (newestVideo == null ||
1468 (v.getAddedAt() > newestVideo.getAddedAt()))
1469 newestVideo = v;
1470 if (v instanceof TVShow) {
1471 TVShow tv = (TVShow)v;
1472 for (TVSeason season : tv.getSeasons()) {
1473 if (season.getAddedAt() > newestVideo.getAddedAt()) {
1474 newestVideo = tv;
1475 }
1476 }
1477 }
1478 }
1479 if (newestVideo != null) {
1480 log.debug("recentCheck: newest video: "
1481 + newestVideo.getTitle()
1482 + " d:" + newestVideo.getAddedAt());
1483 if (library.getNewestVideo().getAddedAt() != newestVideo.getAddedAt()) {
1484 log.info("library updated. Reloading");
1485 loadLibrary();
1486 lastUpdateTime = new Date().getTime();
1487 }
1488 }
1489 }
1490 }
1491
1492 }