serial-watcher 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/bin/serial-watcher +13 -0
- data/serial-watcher.gemspec +19 -0
- data/src/Gemfile +8 -0
- data/src/episode.rb +12 -0
- data/src/fs.rb +103 -0
- data/src/gui/list_model.rb +35 -0
- data/src/gui/list_view.rb +46 -0
- data/src/gui/main.rb +224 -0
- data/src/gui/meta_dialog.rb +42 -0
- data/src/gui/settings_dialog.rb +44 -0
- data/src/gui/title_box.rb +73 -0
- data/src/gui/torrent_panel.rb +42 -0
- data/src/main.rb +53 -0
- data/src/meta.rb +43 -0
- data/src/player.rb +58 -0
- data/src/plugins/torrent/main.rb +29 -0
- data/src/plugins/torrent/torrentz.rb +25 -0
- data/src/plugins/torrent/tpb.rb +25 -0
- data/src/serial.rb +27 -0
- data/src/status.rb +86 -0
- data/static/images/app.png +0 -0
- data/static/images/dash.png +0 -0
- data/static/images/fullscreen.png +0 -0
- data/static/images/round.png +0 -0
- data/tmp/install.rb +38 -0
- data/tmp/sendkey.sh +5 -0
- data/tmp/vimsession.vim +2348 -0
- metadata +169 -0
data/.gitignore
ADDED
data/bin/serial-watcher
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH << File.expand_path(File.join(__FILE__, "../../", "src"))
|
4
|
+
|
5
|
+
$STATIC_PATH = File.expand_path(File.join(__FILE__, "../../static/"))
|
6
|
+
|
7
|
+
require 'main'
|
8
|
+
|
9
|
+
SerialWatcher.new
|
10
|
+
|
11
|
+
# #!/bin/bash -l
|
12
|
+
# cd "`dirname $0`/../src"
|
13
|
+
# bundle exec ruby serialwatcher.rb
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'serial-watcher'
|
3
|
+
s.version = '0.2.1'
|
4
|
+
s.date = '2012-09-17'
|
5
|
+
s.summary = "Serial Watcher"
|
6
|
+
s.description = "Soft to watch serials :)"
|
7
|
+
s.authors = ["rimek"]
|
8
|
+
s.email = 'rimek@poczta.fm'
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.homepage = 'https://github.com/rimek/serialwatcher'
|
11
|
+
s.executables << 'serial-watcher'
|
12
|
+
|
13
|
+
s.add_runtime_dependency 'gtk2'
|
14
|
+
s.add_runtime_dependency 'naturalsort'
|
15
|
+
s.add_runtime_dependency 'simple-rss'
|
16
|
+
s.add_runtime_dependency 'open4'
|
17
|
+
s.add_runtime_dependency 'mplayer-ruby'
|
18
|
+
s.add_runtime_dependency 'lockfile'
|
19
|
+
end
|
data/src/Gemfile
ADDED
data/src/episode.rb
ADDED
data/src/fs.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
require 'natural_sort'
|
4
|
+
include NaturalSort
|
5
|
+
|
6
|
+
require 'episode'
|
7
|
+
require 'serial'
|
8
|
+
|
9
|
+
class Fs
|
10
|
+
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
attr_reader :series
|
14
|
+
attr_reader :episodes
|
15
|
+
|
16
|
+
def init(dir)
|
17
|
+
@main_directory = dir
|
18
|
+
#puts "fs:init #{dir}"
|
19
|
+
clear
|
20
|
+
end
|
21
|
+
|
22
|
+
def series
|
23
|
+
@series ||= load_series
|
24
|
+
end
|
25
|
+
|
26
|
+
def episodes serial
|
27
|
+
@episodes ||= load_episodes serial
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear
|
31
|
+
@series = @episodes = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def read_directory dir
|
36
|
+
return nil if not Dir.exists?(dir)
|
37
|
+
dirs = Dir.entries(dir)
|
38
|
+
dirs.delete(".")
|
39
|
+
dirs.delete("..")
|
40
|
+
dirs.natural_sort
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_series
|
44
|
+
series_directories = read_directory @main_directory
|
45
|
+
return if not series_directories
|
46
|
+
|
47
|
+
counter = 1
|
48
|
+
data = []
|
49
|
+
series_directories.each do |serial_directory_name|
|
50
|
+
path = File.expand_path "#{@main_directory}/#{serial_directory_name}"
|
51
|
+
next if not File.directory?(path)
|
52
|
+
|
53
|
+
name = serial_directory_name
|
54
|
+
|
55
|
+
serial = Serial.new( :name => name,
|
56
|
+
:path => path,
|
57
|
+
:counter => counter
|
58
|
+
)
|
59
|
+
data.push(serial)
|
60
|
+
counter += 1
|
61
|
+
end
|
62
|
+
data
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_episodes serial
|
66
|
+
episode_files = read_directory serial.path
|
67
|
+
return if not episode_files
|
68
|
+
|
69
|
+
data = []
|
70
|
+
counter = 0;
|
71
|
+
episode_files.each do |file|
|
72
|
+
|
73
|
+
next if File.directory?(file)
|
74
|
+
next if not ['.mp4', '.mkv', '.mpg', '.avi', '.flv'].include?(File.extname(file).downcase)
|
75
|
+
|
76
|
+
name = file
|
77
|
+
nr = counter
|
78
|
+
counter += 1
|
79
|
+
|
80
|
+
name = name[0..-5]
|
81
|
+
name.strip!
|
82
|
+
|
83
|
+
path = File.expand_path serial.path+'/'+file
|
84
|
+
next if File.directory?(path)
|
85
|
+
|
86
|
+
episode = Episode.new(:name => name,
|
87
|
+
:path => path,
|
88
|
+
:number => nr,
|
89
|
+
:serial => serial
|
90
|
+
)
|
91
|
+
data.push(episode)
|
92
|
+
end
|
93
|
+
|
94
|
+
data.sort! do |a,b|
|
95
|
+
a.number <=> b.number
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
module Gui
|
3
|
+
class ListModel < Gtk::ListStore
|
4
|
+
|
5
|
+
attr_accessor :episodes, :serial, :counter
|
6
|
+
|
7
|
+
def initialize()
|
8
|
+
super String, Integer
|
9
|
+
@counter = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def load serial
|
13
|
+
clear
|
14
|
+
@counter = 0
|
15
|
+
@episodes = []
|
16
|
+
@serial = serial
|
17
|
+
|
18
|
+
fs = Fs.instance
|
19
|
+
fs.init serial
|
20
|
+
fs.episodes(serial).each do |episode|
|
21
|
+
item = append
|
22
|
+
item.set_value 0, episode.name
|
23
|
+
item.set_value 1, episode.number
|
24
|
+
|
25
|
+
@episodes << episode
|
26
|
+
@counter += 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def reload_episodes(serial)
|
31
|
+
load serial
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module Gui
|
3
|
+
class ListView < Gtk::TreeView
|
4
|
+
COLUMNS = %w( Name )
|
5
|
+
|
6
|
+
def initialize(model = nil)
|
7
|
+
super model
|
8
|
+
|
9
|
+
cell_text = Gtk::CellRendererText.new
|
10
|
+
COLUMNS.each_with_index do |name, idx|
|
11
|
+
col = Gtk::TreeViewColumn.new name, cell_text, :text => idx
|
12
|
+
col.resizable = true
|
13
|
+
append_column col
|
14
|
+
end
|
15
|
+
|
16
|
+
@status = Status.instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def preselect_row serial
|
20
|
+
initial_episode_number = @status.get_last_episode_number serial
|
21
|
+
self.select_custom_row initial_episode_number
|
22
|
+
end
|
23
|
+
|
24
|
+
def preselect_row_via_episode episode
|
25
|
+
self.select_custom_row episode.number
|
26
|
+
end
|
27
|
+
|
28
|
+
def select_custom_row row_number
|
29
|
+
self.model.each do |model, path, iter|
|
30
|
+
if iter.get_value(1) == row_number.to_i
|
31
|
+
|
32
|
+
2.times do
|
33
|
+
path.prev! if path.to_s.to_i > 0
|
34
|
+
end
|
35
|
+
|
36
|
+
self.grab_focus
|
37
|
+
self.set_cursor(path, nil, false)
|
38
|
+
self.scroll_to_cell(path, nil, false, 0, 0)
|
39
|
+
|
40
|
+
self.selection.select_iter(iter)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
data/src/gui/main.rb
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
|
2
|
+
require 'gtk2'
|
3
|
+
|
4
|
+
require 'gui/list_model'
|
5
|
+
require 'gui/list_view'
|
6
|
+
require 'gui/title_box'
|
7
|
+
require 'gui/torrent_panel'
|
8
|
+
require 'gui/settings_dialog'
|
9
|
+
require 'gui/meta_dialog'
|
10
|
+
require 'plugins/torrent/torrentz'
|
11
|
+
require 'player'
|
12
|
+
require 'fs'
|
13
|
+
|
14
|
+
require 'pry'
|
15
|
+
|
16
|
+
module Gui
|
17
|
+
class Main < Gtk::Window
|
18
|
+
|
19
|
+
def initialize()
|
20
|
+
super
|
21
|
+
|
22
|
+
set_title "Serial Watcher"
|
23
|
+
set_window_position Gtk::Window::POS_CENTER
|
24
|
+
set_default_size 600, 600
|
25
|
+
set_border_width 10
|
26
|
+
|
27
|
+
@player = Player.instance
|
28
|
+
@status = Status.instance
|
29
|
+
|
30
|
+
create_elements
|
31
|
+
create_layout
|
32
|
+
connect_signals
|
33
|
+
|
34
|
+
@settings = Gtk::Settings.new
|
35
|
+
@settings.gtk_button_images = true
|
36
|
+
|
37
|
+
@title_box.load
|
38
|
+
|
39
|
+
#binding.pry
|
40
|
+
|
41
|
+
Gtk::Window.set_default_icon File.join($STATIC_PATH, 'images/app.png')
|
42
|
+
|
43
|
+
show
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_elements
|
47
|
+
@vbox = Gtk::VBox.new
|
48
|
+
@paned_inner = Gtk::VPaned.new
|
49
|
+
|
50
|
+
# paned window
|
51
|
+
@pw = Gtk::HPaned.new
|
52
|
+
|
53
|
+
# scroll view
|
54
|
+
@sw = Gtk::ScrolledWindow.new
|
55
|
+
@sw.set_policy Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC
|
56
|
+
|
57
|
+
# tree view
|
58
|
+
@model = ListModel.new
|
59
|
+
@view = ListView.new @model
|
60
|
+
|
61
|
+
# directory chooser
|
62
|
+
@path_label = Gtk::Label.new("", true)
|
63
|
+
@path_label.set_height_request 30
|
64
|
+
|
65
|
+
# change episode series
|
66
|
+
@title_box = TitleBox.new
|
67
|
+
@title_scroll_panel = Gtk::ScrolledWindow.new
|
68
|
+
@title_scroll_panel.add_with_viewport @title_box
|
69
|
+
@title_scroll_panel.set_hscrollbar_policy 'never'
|
70
|
+
viewport = @title_scroll_panel.child
|
71
|
+
viewport.set_shadow_type Gtk::SHADOW_NONE
|
72
|
+
|
73
|
+
#scroll.vadjustment.value = scroll.vadjustment.upper - scroll.vadjustment.page_sizep
|
74
|
+
|
75
|
+
# torrent panel
|
76
|
+
@torrent_panel = TorrentPanel.new
|
77
|
+
|
78
|
+
#toolbar
|
79
|
+
@toolbar = Gtk::Toolbar.new
|
80
|
+
@toolbar.set_toolbar_style Gtk::Toolbar::Style::BOTH # TEXT
|
81
|
+
|
82
|
+
# round play tool item
|
83
|
+
Gtk::Stock.add(:round, " Round play ")
|
84
|
+
icon = Gtk::Image.new "image/round.png"
|
85
|
+
@round_tool = Gtk::ToggleToolButton.new :round
|
86
|
+
@round_tool.active = true if @status.data[:round]
|
87
|
+
|
88
|
+
# round play tool item
|
89
|
+
Gtk::Stock.add(:fullscreen, " Fullscreen ")
|
90
|
+
icon = Gtk::Image.new "image/fullscreen.png"
|
91
|
+
@fullscreen_tool = Gtk::ToggleToolButton.new :fullscreen
|
92
|
+
@fullscreen_tool.active = true if @status.data[:fullscreen]
|
93
|
+
|
94
|
+
# meta edit tool item
|
95
|
+
Gtk::Stock.add(:meta_edit, " Meta edit ")
|
96
|
+
@meta_edit_tool = Gtk::ToolButton.new :meta_edit
|
97
|
+
@meta_edit_tool.sensitive = false
|
98
|
+
|
99
|
+
# settings tool item
|
100
|
+
Gtk::Stock.add(:settings, " Settings ")
|
101
|
+
@settings_tool = Gtk::ToolButton.new :settings
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_layout
|
105
|
+
separator = Gtk::SeparatorToolItem.new
|
106
|
+
space = Gtk::SeparatorToolItem.new
|
107
|
+
space.expand = true
|
108
|
+
space.draw= false
|
109
|
+
@toolbar.insert 0, @round_tool
|
110
|
+
@toolbar.insert 1, @fullscreen_tool
|
111
|
+
@toolbar.insert 2, space
|
112
|
+
@toolbar.insert 3, @meta_edit_tool
|
113
|
+
@toolbar.insert 4, separator
|
114
|
+
@toolbar.insert 5, @settings_tool
|
115
|
+
|
116
|
+
@sw.add @view
|
117
|
+
|
118
|
+
@paned_inner.set_size_request(200, -1)
|
119
|
+
@paned_inner.pack1(@sw, true, false)
|
120
|
+
@paned_inner.pack2(@torrent_panel, false, false)
|
121
|
+
|
122
|
+
@pw.add @title_scroll_panel
|
123
|
+
@pw.add @paned_inner
|
124
|
+
|
125
|
+
|
126
|
+
@vbox.pack_start @toolbar, false, true
|
127
|
+
@vbox.pack_start @path_label, false, true
|
128
|
+
@vbox.pack_end @pw
|
129
|
+
|
130
|
+
add @vbox
|
131
|
+
end
|
132
|
+
|
133
|
+
def connect_signals
|
134
|
+
|
135
|
+
self.signal_connect "destroy" do
|
136
|
+
Gtk.main_quit()
|
137
|
+
end
|
138
|
+
|
139
|
+
#toolbar buttons
|
140
|
+
@round_tool.signal_connect "toggled" do |button|
|
141
|
+
data = { :round => @round_tool.active? }
|
142
|
+
@status.save_config data
|
143
|
+
end
|
144
|
+
@fullscreen_tool.signal_connect "toggled" do |button|
|
145
|
+
data = { :fullscreen => @fullscreen_tool.active? }
|
146
|
+
@status.save_config data
|
147
|
+
end
|
148
|
+
|
149
|
+
@settings_tool.signal_connect "clicked" do |button|
|
150
|
+
settings = SettingsDialog.new #self
|
151
|
+
settings.signal_connect "response" do |sender, response|
|
152
|
+
if response == Gtk::Dialog::RESPONSE_OK
|
153
|
+
@status.save_config settings.values
|
154
|
+
if settings.path_changed?
|
155
|
+
@model.clear
|
156
|
+
@meta_edit_tool.sensitive = false
|
157
|
+
@title_box.load
|
158
|
+
end
|
159
|
+
end
|
160
|
+
settings.destroy
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
@meta_edit_tool.signal_connect "clicked" do |button|
|
165
|
+
return if not serial = @model.serial
|
166
|
+
|
167
|
+
meta = MetaDialog.new :serial => serial
|
168
|
+
meta.signal_connect "response" do |sender, response|
|
169
|
+
if response == Gtk::Dialog::RESPONSE_OK
|
170
|
+
serial.meta.save_file meta.values
|
171
|
+
change_serial serial
|
172
|
+
end
|
173
|
+
meta.destroy
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
# change single episode (play)
|
179
|
+
@view.signal_connect "row_activated" do |view, path, column|
|
180
|
+
@current_episode_path = path
|
181
|
+
self.play_episode @current_episode_path
|
182
|
+
end
|
183
|
+
|
184
|
+
@player.signal_connect "playing_ended" do |sender, param|
|
185
|
+
if @round_tool.active?
|
186
|
+
@current_episode_path.next!
|
187
|
+
self.play_episode @current_episode_path
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# change serial
|
192
|
+
@title_box.signal_connect "title_changed" do |titlebox, serial|
|
193
|
+
change_serial serial
|
194
|
+
end
|
195
|
+
|
196
|
+
# load serial list
|
197
|
+
@title_box.signal_connect "loaded" do |titlebox|
|
198
|
+
change_path_label
|
199
|
+
end
|
200
|
+
|
201
|
+
Gtk::LinkButton.set_uri_hook do end
|
202
|
+
end
|
203
|
+
|
204
|
+
def play_episode path
|
205
|
+
iter = @model.get_iter(path)
|
206
|
+
episode = @model.episodes[(@model.get_value(iter, 1).to_i)]# - 1]
|
207
|
+
@player.play_episode(episode)
|
208
|
+
@view.preselect_row_via_episode episode
|
209
|
+
end
|
210
|
+
|
211
|
+
def change_serial serial
|
212
|
+
@model.reload_episodes serial
|
213
|
+
@view.preselect_row serial
|
214
|
+
@torrent_panel.load serial
|
215
|
+
@meta_edit_tool.sensitive = true
|
216
|
+
end
|
217
|
+
|
218
|
+
def change_path_label
|
219
|
+
@path_label.text = "Current serial directory: #{@status.data_dir}"
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|