aniview 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/aniview/interface/animeio/animefile.rb +7 -4
- data/lib/aniview/interface/animeio/animeio.rb +97 -39
- data/lib/aniview/interface/animeio/animeseries.rb +20 -2
- data/lib/aniview/interface/mpv/mpvbridge.rb +27 -7
- data/lib/aniview/interface/pref/defaults.json +10 -5
- data/lib/aniview/interface/pref/pref.rb +22 -1
- data/lib/aniview/interface/pref/validate.json +4 -0
- data/lib/aniview/interface/subscription/subscription.rb +0 -4
- data/lib/aniview/util/folder_listen.rb +53 -0
- data/lib/aniview/util/term.rb +6 -5
- data/lib/aniview/util/util.rb +20 -7
- data/lib/aniview/view/aiomenu.rb +45 -39
- data/lib/aniview/view/menu.rb +6 -15
- data/lib/aniview/view/prefmenu.rb +7 -2
- data/lib/application.rb +47 -30
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ea930584ab074643f3257cfb4ead82380d28a8e
|
4
|
+
data.tar.gz: dd51a52724248c507c8fcf171181ed73aa30acd0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 337ae5115ba6619e0e526e8ef7d8dfc424671c36c8698d4e5b39dddb24fd6f873b715c8c383805e21d48f4cd33fc4d1ab9b7ba4cae0e16c4bf1cec8e95c813fe
|
7
|
+
data.tar.gz: 657aee82a1a6ac61017368fd4d5238813535a25dcfe36d0c7326be23883188e58b1811786bfa06ce5124d0ac885c082ea84efbbaaff70eb35fa4f34afc44222c
|
data/README.md
CHANGED
@@ -7,6 +7,6 @@ command line anime library viewer
|
|
7
7
|
### using rubygems
|
8
8
|
`gem install aniview`
|
9
9
|
|
10
|
-
|
11
|
-
+ [
|
12
|
-
+ [
|
10
|
+
## documentation
|
11
|
+
+ [rubydoc.info](http://www.rubydoc.info/gems/aniview/)
|
12
|
+
+ [wiki](https://github.com/annacrombie/aniview/wiki/)
|
@@ -14,7 +14,7 @@ module Aniview
|
|
14
14
|
attr_accessor :accessible
|
15
15
|
attr_accessor :path
|
16
16
|
|
17
|
-
def initialize(path_, seen
|
17
|
+
def initialize(path_, seen: false, watched_on: 0)
|
18
18
|
@watched = seen
|
19
19
|
@accessible = true
|
20
20
|
begin
|
@@ -22,7 +22,7 @@ module Aniview
|
|
22
22
|
rescue Errno::ENOENT
|
23
23
|
@last_modified = "0"
|
24
24
|
end
|
25
|
-
@watched_on =
|
25
|
+
@watched_on = watched_on
|
26
26
|
@path = path_
|
27
27
|
|
28
28
|
@attr = {
|
@@ -37,6 +37,7 @@ module Aniview
|
|
37
37
|
"l" => "",
|
38
38
|
"f" => "",
|
39
39
|
"b" => "",
|
40
|
+
"w" => seen? ? "x" : " "
|
40
41
|
}
|
41
42
|
|
42
43
|
if (@path == "empty")
|
@@ -55,15 +56,15 @@ module Aniview
|
|
55
56
|
"l" => mov.colorspace,
|
56
57
|
"f" => mov.frame_rate,
|
57
58
|
"b" => String(mov.bitrate) + "kb/s",
|
59
|
+
"w" => seen? ? "x" : " "
|
58
60
|
}
|
59
61
|
end
|
60
|
-
|
61
62
|
end
|
62
63
|
|
63
64
|
def title
|
64
65
|
return @attr["t"]
|
65
66
|
end
|
66
|
-
|
67
|
+
|
67
68
|
def seen?
|
68
69
|
return @watched
|
69
70
|
end
|
@@ -75,10 +76,12 @@ module Aniview
|
|
75
76
|
def watch
|
76
77
|
@watched = true
|
77
78
|
@watched_on = DateTime.now.strftime('%Q')
|
79
|
+
@attr["w"] = "x"
|
78
80
|
end
|
79
81
|
|
80
82
|
def unwatch
|
81
83
|
@watched = false
|
84
|
+
@attr["w"] = " "
|
82
85
|
end
|
83
86
|
|
84
87
|
def string
|
@@ -7,6 +7,7 @@ require 'benchmark'
|
|
7
7
|
require_relative 'animefile'
|
8
8
|
require_relative 'animeseries'
|
9
9
|
require_relative '../../util/util'
|
10
|
+
require_relative '../../util/folder_listen'
|
10
11
|
require_relative '../bridge'
|
11
12
|
|
12
13
|
module Aniview
|
@@ -27,24 +28,34 @@ module Aniview
|
|
27
28
|
@made_changes = true
|
28
29
|
|
29
30
|
self.load
|
30
|
-
pref_changed!
|
31
|
-
relisten
|
31
|
+
pref_changed!(verbose: false)
|
32
|
+
#relisten
|
32
33
|
|
33
34
|
@pref.add_observer(self, :pref_changed!)
|
34
35
|
@mpvbridge.add_observer(self, :animefile_changed!)
|
35
36
|
#@@local_anime.each { |key, obj| obj.add_observer(self, :animefile_changed!)}
|
36
37
|
end
|
37
38
|
|
39
|
+
def listen_to_all_dirs
|
40
|
+
#listen to all
|
41
|
+
all_dirs = (@pref.get("anime_locations").split(":")).map { |dir| @pref.parseDir(dir) }
|
42
|
+
@folder_listener = FolderListen.to(all_dirs) { index_anime }
|
43
|
+
@folder_listener.start
|
44
|
+
end
|
45
|
+
|
38
46
|
def relisten
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
47
|
+
cleanup
|
48
|
+
|
49
|
+
listen_to_all_dirs
|
50
|
+
|
51
|
+
return if @anime_dirs == nil or @anime_dirs[0] == "" or @anime_dirs.length == 0
|
52
|
+
|
53
|
+
@listener = Listen.to(*@anime_dirs, only: /\.mkv$/) { index_anime }
|
44
54
|
@listener.start
|
45
55
|
end
|
46
56
|
|
47
57
|
def cleanup
|
58
|
+
@folder_listener.stop unless @folder_listener == nil
|
48
59
|
@listener.stop unless @listener == nil
|
49
60
|
end
|
50
61
|
|
@@ -66,23 +77,24 @@ module Aniview
|
|
66
77
|
end
|
67
78
|
end
|
68
79
|
|
69
|
-
def pref_changed!
|
80
|
+
def pref_changed!(verbose: true)
|
70
81
|
new_dirs = (@pref.get("anime_locations").split(":")).collect { |dir|
|
71
|
-
@pref.parseDir(dir)
|
72
|
-
|
82
|
+
parsed_dir = @pref.parseDir(dir)
|
83
|
+
parsed_dir if File.exist?(parsed_dir)
|
84
|
+
}.compact
|
73
85
|
if new_dirs != @anime_dirs
|
74
86
|
@anime_dirs = new_dirs
|
75
|
-
index_anime
|
87
|
+
index_anime(verbose: verbose)
|
76
88
|
relisten
|
77
89
|
end
|
78
90
|
end
|
79
91
|
|
80
92
|
def animefile_changed!
|
81
|
-
return unless @mpvbridge.what_changed
|
93
|
+
return unless @mpvbridge.what_changed[:local_anime]
|
94
|
+
save
|
82
95
|
changed
|
83
96
|
notify_observers
|
84
97
|
end
|
85
|
-
|
86
98
|
|
87
99
|
##
|
88
100
|
## @brief Indexes anime files up to 1 level deep in specified anime directories
|
@@ -92,24 +104,36 @@ module Aniview
|
|
92
104
|
##
|
93
105
|
## @return { description_of_the_return_value }
|
94
106
|
##
|
95
|
-
def index_anime(force: false, verbose: false)
|
107
|
+
def index_anime(force: false, verbose: true, very_verbose: false)
|
108
|
+
puts "Indexing anime" if very_verbose
|
96
109
|
Thread.new {
|
97
110
|
ret = {}
|
98
111
|
made_changes = false
|
99
112
|
lal = @@local_anime.length
|
100
113
|
|
101
|
-
@anime_dirs.
|
114
|
+
@anime_dirs.each_with_index { |dir, i|
|
115
|
+
|
116
|
+
avs_file = dir + "/.avs"
|
117
|
+
|
118
|
+
if File.exist?(avs_file)
|
119
|
+
puts "found avs_data" if very_verbose
|
120
|
+
avs_data = JSON.parse(File.open(avs_file, "r") { |f| f.read })
|
121
|
+
else
|
122
|
+
puts "no avs_data" if very_verbose
|
123
|
+
avs_data = {}
|
124
|
+
end
|
125
|
+
|
102
126
|
|
103
127
|
# check if the directory exists
|
104
|
-
|
105
|
-
|
128
|
+
unless File.exist? dir
|
129
|
+
unless @dir_last_modified[dir] == "check"
|
106
130
|
@dir_last_modified[dir] = "check"
|
107
131
|
made_changes = true
|
108
132
|
end
|
109
133
|
next
|
110
134
|
end
|
111
135
|
|
112
|
-
puts "checking #{dir}" if
|
136
|
+
puts "checking #{dir}" if very_verbose
|
113
137
|
|
114
138
|
# skip indexing a directory if it hasn't been modified since the last
|
115
139
|
# check (disabled because doesn't save to much time)
|
@@ -129,15 +153,37 @@ module Aniview
|
|
129
153
|
# get all files in the directory and in all subfolders of the directory
|
130
154
|
files = Dir.glob("#{dir}*/*.{mkv,avi,mp4}") + Dir.glob("#{dir}/*.{mkv,avi,mp4}")
|
131
155
|
|
132
|
-
files.
|
133
|
-
|
134
|
-
|
156
|
+
files.each_with_index { |file, j|
|
157
|
+
if verbose
|
158
|
+
pct = Util.format_progress ((j + 1) / Float(files.length)) * ( (i + 1) / Float(@anime_dirs.length)) * 100
|
159
|
+
Util.error_message("#{pct}% #{File.basename(file)}")
|
160
|
+
end
|
161
|
+
|
162
|
+
print "indexing #{file} -> " if very_verbose
|
163
|
+
params = {:seen => false, :watched_on => 0}
|
164
|
+
|
165
|
+
#check if this anime has a stored watched value
|
166
|
+
if avs_data.key?(file)
|
167
|
+
puts "found stored data for this file" if very_verbose
|
168
|
+
params = {
|
169
|
+
:seen => avs_data[file]["seen"],
|
170
|
+
:watched_on => avs_data[file]["watched_on"]
|
171
|
+
}
|
172
|
+
puts params if very_verbose
|
173
|
+
else
|
174
|
+
puts "no data on this file" if very_verbose
|
175
|
+
avs_data.merge!({file => params})
|
176
|
+
end
|
135
177
|
|
136
178
|
# we have already indexed this file
|
137
179
|
if @@local_anime != nil and @@local_anime.key?(file)
|
138
180
|
|
181
|
+
puts "this file is in the index" if very_verbose
|
182
|
+
|
139
183
|
# get its seen data
|
140
|
-
|
184
|
+
params[:seen] = @@local_anime[file].seen?
|
185
|
+
params[:watched_on] = @@local_anime[file].seenOn
|
186
|
+
puts params if very_verbose
|
141
187
|
# get its last modifed data
|
142
188
|
last_modified = File::Stat.new(file).mtime
|
143
189
|
# get wether or not it is accessible
|
@@ -146,7 +192,7 @@ module Aniview
|
|
146
192
|
# if we arent forcing or the anime hasn't been modified since the
|
147
193
|
# last check, skip it
|
148
194
|
if not force or last_modified == @@local_anime[file].last_modified
|
149
|
-
puts "unchanged" if
|
195
|
+
puts "unchanged" if very_verbose
|
150
196
|
ret.merge!(file => @@local_anime[file])
|
151
197
|
next
|
152
198
|
end
|
@@ -159,12 +205,15 @@ module Aniview
|
|
159
205
|
|
160
206
|
# this is the anime file we are adding
|
161
207
|
ret.merge!(
|
162
|
-
file => AnimeFile.new(
|
163
|
-
file,
|
164
|
-
seen_this
|
165
|
-
)
|
208
|
+
file => AnimeFile.new(file, params)
|
166
209
|
)
|
167
210
|
}
|
211
|
+
|
212
|
+
mavsf = @pref.get("avs_files")["create?"]
|
213
|
+
oimfs = @pref.get("avs_files")["only_on_mounted_fs?"]
|
214
|
+
if mavsf and ((oimfs and Util.mounted_filesystem?(dir)) or not oimfs)
|
215
|
+
File.open(avs_file, "w") { |f| f.puts JSON.generate(avs_data)}
|
216
|
+
end
|
168
217
|
}
|
169
218
|
|
170
219
|
#if @@local_anime != nil
|
@@ -184,6 +233,7 @@ module Aniview
|
|
184
233
|
changed
|
185
234
|
notify_observers
|
186
235
|
end
|
236
|
+
#
|
187
237
|
}
|
188
238
|
end
|
189
239
|
|
@@ -201,7 +251,7 @@ module Aniview
|
|
201
251
|
hash = {}
|
202
252
|
|
203
253
|
arr.sort_by {|i| i.title}.each { |file|
|
204
|
-
|
254
|
+
unless keys.key?(parent file.path)
|
205
255
|
key = AnimeSeries.new parent(file.path)
|
206
256
|
keys.merge! parent(file.path) => key
|
207
257
|
hash.merge! key => []
|
@@ -210,7 +260,7 @@ module Aniview
|
|
210
260
|
end
|
211
261
|
|
212
262
|
hash[key] << file
|
213
|
-
key
|
263
|
+
key << file
|
214
264
|
}
|
215
265
|
hash.sort_by{ |j| j[0].title }.to_h
|
216
266
|
end
|
@@ -230,10 +280,10 @@ module Aniview
|
|
230
280
|
f.puts %x(date).chomp + " ./" + File.basename(file) + " " + @watch_log_tag
|
231
281
|
end
|
232
282
|
end
|
233
|
-
|
283
|
+
|
234
284
|
def addWatched(file)
|
235
285
|
@@local_anime[file].watch
|
236
|
-
|
286
|
+
save
|
237
287
|
changed
|
238
288
|
notify_observers
|
239
289
|
end
|
@@ -241,15 +291,14 @@ module Aniview
|
|
241
291
|
def rmWatched(path = "")
|
242
292
|
if @@local_anime.key? path
|
243
293
|
@@local_anime[path].unwatch
|
244
|
-
|
294
|
+
else
|
295
|
+
r = []
|
296
|
+
@@local_anime.values.each_with_index{ |v, i| r << {Integer(v.seenOn) => @@local_anime.keys[i]} if v.seen? }
|
297
|
+
return if r.length == 0
|
298
|
+
last_watched = (r.sort_by{ |h| h.keys.first}.reverse)[0].values[0]
|
299
|
+
@@local_anime[last_watched].unwatch
|
245
300
|
end
|
246
|
-
|
247
|
-
r = []
|
248
|
-
@@local_anime.values.each_with_index{ |v, i| r << {Integer(v.seenOn) => @@local_anime.keys[i]} if v.seen? }
|
249
|
-
return if r.length == 0
|
250
|
-
last_watched = (r.sort_by{ |h| h.keys.first}.reverse)[0].values[0]
|
251
|
-
@@local_anime[last_watched].unwatch
|
252
|
-
self.save
|
301
|
+
save
|
253
302
|
changed
|
254
303
|
notify_observers
|
255
304
|
end
|
@@ -258,6 +307,15 @@ module Aniview
|
|
258
307
|
@mpvbridge.play file
|
259
308
|
end
|
260
309
|
|
310
|
+
def watch_list(files)
|
311
|
+
File.open("/tmp/aniview_list.m3u", "w") { |f|
|
312
|
+
files.each { |file|
|
313
|
+
f.puts file.path
|
314
|
+
}
|
315
|
+
}
|
316
|
+
@mpvbridge.playlist("/tmp/aniview_list.m3u", files.map { |f| [f.path, f] }.to_h)
|
317
|
+
end
|
318
|
+
|
261
319
|
end
|
262
320
|
end
|
263
321
|
end
|
@@ -16,6 +16,9 @@ module Aniview
|
|
16
16
|
"c" => 0,
|
17
17
|
"d" => 0,
|
18
18
|
"s" => 0,
|
19
|
+
"p" => 0,
|
20
|
+
"w" => 0,
|
21
|
+
"W" => 0
|
19
22
|
}
|
20
23
|
|
21
24
|
@children = children
|
@@ -24,10 +27,25 @@ module Aniview
|
|
24
27
|
|
25
28
|
def + af
|
26
29
|
@attr["d"] += af.attr["d"]
|
27
|
-
@attr["D"] = Util.format_duration
|
30
|
+
@attr["D"] = Util.format_duration(@attr["d"])
|
28
31
|
@attr["s"] += af.attr["s"]
|
29
|
-
@attr["S"] = Util.format_size
|
32
|
+
@attr["S"] = Util.format_size(@attr["s"])
|
30
33
|
@attr["c"] += 1
|
34
|
+
@attr["w"] += 1 if af.seen?
|
35
|
+
@attr["W"] += af.attr["d"] if af.seen?
|
36
|
+
end
|
37
|
+
|
38
|
+
def << af
|
39
|
+
@children << af
|
40
|
+
self + af
|
41
|
+
average
|
42
|
+
end
|
43
|
+
|
44
|
+
def average
|
45
|
+
@attr["p"] = @attr["w"] * 100.0 / @attr["c"]
|
46
|
+
@attr["q"] = @attr["d"] > 0 ? @attr["W"] * 100.0 / @attr["d"] : 0
|
47
|
+
@attr["Q"] = Util.format_progress @attr["q"]
|
48
|
+
@attr["P"] = Util.format_progress @attr["p"]
|
31
49
|
end
|
32
50
|
|
33
51
|
def title
|
@@ -11,12 +11,13 @@ module Aniview
|
|
11
11
|
include Observable
|
12
12
|
|
13
13
|
attr_accessor :playing
|
14
|
+
attr_accessor :playing_file
|
14
15
|
attr_accessor :what_changed
|
15
16
|
|
16
17
|
def initialize pref
|
17
18
|
@pref = pref
|
18
19
|
@mpv_enabled = true
|
19
|
-
@logger = Logger.new('mpv.log')
|
20
|
+
@logger = Logger.new(@pref.parseDir('$conf_dir/mpv.log'))
|
20
21
|
@logger.level = Logger::DEBUG
|
21
22
|
connect
|
22
23
|
@what_changed = ""
|
@@ -24,17 +25,25 @@ module Aniview
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def event_handler event
|
27
|
-
|
28
|
+
@logger.debug event
|
29
|
+
case event["event"]
|
30
|
+
#file-loaded
|
28
31
|
when "start-file"
|
29
32
|
@playing = true
|
30
|
-
@what_changed =
|
33
|
+
@what_changed = {:playing_status => :start}
|
31
34
|
changed
|
32
35
|
notify_observers
|
33
36
|
when "end-file"
|
34
37
|
@playing = false
|
35
|
-
@what_changed =
|
38
|
+
@what_changed = {:playing_status => :end}
|
36
39
|
changed
|
37
40
|
notify_observers
|
41
|
+
when "property-change"
|
42
|
+
case event["name"]
|
43
|
+
when "path"
|
44
|
+
@playing_file = @playing_files[event["data"]]
|
45
|
+
end
|
46
|
+
else
|
38
47
|
end
|
39
48
|
end
|
40
49
|
|
@@ -50,6 +59,8 @@ module Aniview
|
|
50
59
|
begin
|
51
60
|
@mpv = MPV::Session.new(user_args: @pref.get("mpv_args").split(" "))
|
52
61
|
@mpv.callbacks << MPV::Callback.new(self, :event_handler)
|
62
|
+
@mpv.client.command("observe_property_string", 1, "path")
|
63
|
+
#@mpv.client.command("observe_property_string", 1, "playback-abort")
|
53
64
|
rescue MPV::MPVNotAvailableError
|
54
65
|
rescue MPV::MPVUnsupportedFlagError
|
55
66
|
else
|
@@ -60,11 +71,20 @@ module Aniview
|
|
60
71
|
def play file
|
61
72
|
return unless @mpv_enabled
|
62
73
|
connect
|
63
|
-
@
|
74
|
+
@playing_files = {file.path => file}
|
64
75
|
@mpv.client.command "loadfile", file.path
|
65
76
|
@playing = true
|
66
77
|
end
|
67
|
-
|
78
|
+
|
79
|
+
def playlist playlist, files
|
80
|
+
return unless @mpv_enabled
|
81
|
+
connect
|
82
|
+
@playing_files = files
|
83
|
+
#@playing_file = @playing_files[0]
|
84
|
+
@mpv.client.command "loadlist", playlist
|
85
|
+
@playing = true
|
86
|
+
end
|
87
|
+
|
68
88
|
def playing?
|
69
89
|
@playing
|
70
90
|
end
|
@@ -75,7 +95,7 @@ module Aniview
|
|
75
95
|
pct = Float(percentage)
|
76
96
|
if pct >= swp and not @playing_file.seen?
|
77
97
|
@playing_file.watch
|
78
|
-
@what_changed =
|
98
|
+
@what_changed = {:local_anime => :watched}
|
79
99
|
changed
|
80
100
|
notify_observers
|
81
101
|
end
|
@@ -41,14 +41,14 @@
|
|
41
41
|
},
|
42
42
|
"format_library" :
|
43
43
|
{
|
44
|
-
"title" : " %t
|
45
|
-
"parent" : " %t
|
46
|
-
"child" : " %t@
|
44
|
+
"title" : " %t@ ##%w/##%c #####%Q ########%D #######%S ",
|
45
|
+
"parent" : " %t@ ##%w/##%c #####%Q ########%D #######%S ",
|
46
|
+
"child" : " %w %t@ ########%D #######%S "
|
47
47
|
},
|
48
48
|
"format_library_unwatched" :
|
49
49
|
{
|
50
|
-
"title" : " %t@
|
51
|
-
"parent" : " %t
|
50
|
+
"title" : " %t@ ##%w/##%c ########%D #######%S ",
|
51
|
+
"parent" : " %t@ ##%w/##%c ########%D #######%S ",
|
52
52
|
"child" : " %t@ %D %S %r "
|
53
53
|
},
|
54
54
|
"format_torrents" :
|
@@ -89,6 +89,11 @@
|
|
89
89
|
{
|
90
90
|
"port" : "21312"
|
91
91
|
},
|
92
|
+
"avs_files":
|
93
|
+
{
|
94
|
+
"create?": true,
|
95
|
+
"only_on_mounted_fs?": false
|
96
|
+
},
|
92
97
|
"set_watched_percentage" : "80",
|
93
98
|
"log_file" : "$conf_dir/aw.log",
|
94
99
|
"daemon_log_file" : "$conf_dir/server.log",
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'json'
|
3
|
+
require 'observer'
|
3
4
|
|
4
5
|
require_relative 'prefitem'
|
5
6
|
require_relative '../../view/color'
|
@@ -76,6 +77,16 @@ module Aniview
|
|
76
77
|
##
|
77
78
|
def set(trail, destination)
|
78
79
|
trail = [trail] if trail.class == String
|
80
|
+
|
81
|
+
destination = case destination
|
82
|
+
when "true"
|
83
|
+
true
|
84
|
+
when "false"
|
85
|
+
false
|
86
|
+
else
|
87
|
+
destination
|
88
|
+
end
|
89
|
+
|
79
90
|
return unless valid_preference?(trail, destination)
|
80
91
|
|
81
92
|
trail[0...-1].inject(@pref, :fetch)[trail.last] = destination
|
@@ -83,6 +94,14 @@ module Aniview
|
|
83
94
|
changed
|
84
95
|
notify_observers
|
85
96
|
end
|
97
|
+
|
98
|
+
def pref_type(trail)
|
99
|
+
trail.inject(@validations, :fetch)
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_pref(trail)
|
103
|
+
trail.inject(@pref, :fetch)
|
104
|
+
end
|
86
105
|
|
87
106
|
##
|
88
107
|
## @brief validates a preference
|
@@ -96,7 +115,7 @@ module Aniview
|
|
96
115
|
def valid_preference?(trail, destination)
|
97
116
|
skeys = [ "space", "up", "down", "left", "right", "enter" ]
|
98
117
|
|
99
|
-
case trail
|
118
|
+
case pref_type(trail)
|
100
119
|
when "key"
|
101
120
|
destination.length == 1 or skeys.include?(destination)
|
102
121
|
when "path"
|
@@ -105,6 +124,8 @@ module Aniview
|
|
105
124
|
Aniview::View::Color.respond_to?(destination)
|
106
125
|
when "int"
|
107
126
|
destination.scan(/\D/).empty? and destination.length > 1
|
127
|
+
when "boolean"
|
128
|
+
destination.class == TrueClass or destination.class == FalseClass
|
108
129
|
when "locked"
|
109
130
|
false
|
110
131
|
else
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# watch for changes in a folder's existance
|
2
|
+
|
3
|
+
module Aniview
|
4
|
+
module Util
|
5
|
+
module FolderListen
|
6
|
+
|
7
|
+
def self.to(dirs, &block)
|
8
|
+
Listener.new(dirs, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
## @brief Similar to Listen gem but uses polling and is only intended
|
13
|
+
## to be used on a small number of files. the Listen gem
|
14
|
+
## wouldn't support watching an individual FOLDER to see if
|
15
|
+
## the folder went away so this is that!
|
16
|
+
##
|
17
|
+
class Listener
|
18
|
+
def initialize(dirs, &block)
|
19
|
+
@dirs = dirs
|
20
|
+
@block = block
|
21
|
+
@sleep_duration = 1
|
22
|
+
@dir_hash = dir_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def dir_hash
|
26
|
+
@dirs.map { |dir| [dir, File.exist?(dir)]}.to_h
|
27
|
+
end
|
28
|
+
|
29
|
+
def changed?
|
30
|
+
@dir_hash != dir_hash
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
@listen_thread.exit if @listen_thread
|
35
|
+
end
|
36
|
+
|
37
|
+
def start
|
38
|
+
Thread.abort_on_exception = true
|
39
|
+
@listen_thread = Thread.new do
|
40
|
+
while true
|
41
|
+
if changed?
|
42
|
+
@block.call
|
43
|
+
@dir_hash = dir_hash
|
44
|
+
end
|
45
|
+
sleep 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/aniview/util/term.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Aniview
|
2
2
|
module Util
|
3
3
|
class Term
|
4
|
+
|
4
5
|
def initialize
|
5
6
|
@tput=Hash[
|
6
7
|
"smcup" => %x(tput smcup),
|
@@ -12,7 +13,7 @@ module Aniview
|
|
12
13
|
"nobold" => %x(tput sgr0),
|
13
14
|
]
|
14
15
|
end
|
15
|
-
|
16
|
+
|
16
17
|
def save; print @tput["smcup"]; return self; end
|
17
18
|
def restore; print @tput["rmcup"]; return self; end
|
18
19
|
def hide_cursor; print @tput["civis"]; return self; end
|
@@ -23,21 +24,21 @@ module Aniview
|
|
23
24
|
def clear; print "\033[2J"; return self; end
|
24
25
|
def bold; print @tput["bold"]; return self; end
|
25
26
|
def nobold; print @tput["nobold"]; return self; end
|
26
|
-
|
27
|
+
|
27
28
|
def cols; return HighLine::SystemExtensions.terminal_size[0]; end
|
28
29
|
def rows; return HighLine::SystemExtensions.terminal_size[1]; end
|
29
30
|
|
30
31
|
def self.cols; return HighLine::SystemExtensions.terminal_size[0]; end
|
31
32
|
def self.rows; return HighLine::SystemExtensions.terminal_size[1]; end
|
32
|
-
|
33
|
+
|
33
34
|
def getKey
|
34
35
|
return STDIN.getch.gsub("\r", "enter").gsub(" ", "space").gsub("A", "up").gsub("B", "down").gsub("C", "right").gsub("D", "left").gsub("\e", "skip").gsub("[", "skip")
|
35
36
|
end
|
36
|
-
|
37
|
+
|
37
38
|
def reset
|
38
39
|
self.restore.show_cursor.echo_on
|
39
40
|
end
|
40
|
-
|
41
|
+
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
data/lib/aniview/util/util.rb
CHANGED
@@ -9,7 +9,7 @@ module Aniview
|
|
9
9
|
term.show_cursor
|
10
10
|
Readline.completion_append_character = ""
|
11
11
|
Readline.pre_input_hook = -> do
|
12
|
-
Readline.insert_text default
|
12
|
+
Readline.insert_text "#{default}"
|
13
13
|
Readline.redisplay
|
14
14
|
Readline.pre_input_hook = nil
|
15
15
|
end
|
@@ -18,9 +18,9 @@ module Aniview
|
|
18
18
|
return input
|
19
19
|
end
|
20
20
|
|
21
|
-
def self.error_message
|
21
|
+
def self.error_message(message)
|
22
22
|
#use statusline to show error messages
|
23
|
-
print "\e[#{Term.rows};
|
23
|
+
print "\e[#{Term.rows - 1};1H\e[31m #{message}\e[K"
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.format_duration v
|
@@ -101,12 +101,12 @@ module Aniview
|
|
101
101
|
mode = "char"
|
102
102
|
|
103
103
|
elsif mode == "%"
|
104
|
-
addstr = ""
|
105
|
-
|
106
|
-
|
107
|
-
addstr = " " + addstr while addstr.length < (ol + padding_collected)
|
104
|
+
addstr = d.key?(c) ? String(d[c]) : ""
|
105
|
+
padding = padding_collected > addstr.length ? " " * (padding_collected - addstr.length) : ""
|
106
|
+
addstr = padding + addstr
|
108
107
|
add_to += addstr
|
109
108
|
mode = "char"
|
109
|
+
padding_collected = 0
|
110
110
|
|
111
111
|
else
|
112
112
|
add_to += c
|
@@ -150,5 +150,18 @@ module Aniview
|
|
150
150
|
{}
|
151
151
|
end
|
152
152
|
end
|
153
|
+
|
154
|
+
def self.mounted_filesystem? dir
|
155
|
+
%x(mount).split("\n").map { |l|
|
156
|
+
mounted = /on (.*?) /.match(l)[1]
|
157
|
+
if mounted == "/"
|
158
|
+
false
|
159
|
+
else
|
160
|
+
dir.split("/").zip(mounted.split("/")).reduce(true) { |m,v|
|
161
|
+
m and (v[0] == v[1] or v[1] == nil)
|
162
|
+
}
|
163
|
+
end
|
164
|
+
}.reduce { |a,b| a or b}
|
165
|
+
end
|
153
166
|
end
|
154
167
|
end
|
data/lib/aniview/view/aiomenu.rb
CHANGED
@@ -7,56 +7,62 @@ module Aniview
|
|
7
7
|
include Aniview::Util
|
8
8
|
|
9
9
|
def refresh_attributes
|
10
|
+
if @items.length > 0
|
11
|
+
unformatted_P = sum_attribute("p") / @items.length
|
12
|
+
else
|
13
|
+
unformatted_P = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
if sum_attribute("d") > 0
|
17
|
+
unformatted_Q = sum_attribute("W") * 100.0 / sum_attribute("d")
|
18
|
+
else
|
19
|
+
unformatted_Q = 0
|
20
|
+
end
|
10
21
|
@attributes = {
|
11
22
|
"t" => @attributes["t"],
|
12
|
-
"D" => Util.format_duration(
|
13
|
-
"S" => Util.format_size(
|
23
|
+
"D" => Util.format_duration(sum_attribute("d")),
|
24
|
+
"S" => Util.format_size(sum_attribute("s")),
|
25
|
+
"P" => Util.format_progress(unformatted_P),
|
26
|
+
"Q" => Util.format_progress(unformatted_Q),
|
27
|
+
"c" => sum_attribute("c"),
|
28
|
+
"w" => sum_attribute("w")
|
14
29
|
}
|
15
30
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def getSize
|
24
|
-
size = 0
|
25
|
-
@items.each{ |child|
|
26
|
-
size += child[0].attributes["s"]
|
27
|
-
}
|
28
|
-
return size
|
29
|
-
end
|
30
|
-
|
31
|
-
def customControl(key, sel)
|
31
|
+
|
32
|
+
def sum_attribute k
|
33
|
+
@items.inject(0) { |mem, c| mem += c[0].attr[k] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def customControl(key, sel)
|
32
37
|
return if @items == {}
|
33
|
-
|
38
|
+
path = @items.values[sel["out"]][sel["in"]].path
|
34
39
|
|
35
40
|
empty = path == "empty"
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
42
|
+
if key == @pref.get("keybindings")["menu_nav_expand"]
|
43
|
+
expand(sel["out"])
|
44
|
+
elsif key == "r"
|
45
|
+
refresh
|
46
|
+
elsif key == "enter" and not empty
|
47
|
+
|
48
|
+
if sel["in_expanded"]
|
49
|
+
@interface.watch(@items.values[sel["out"]][sel["in"]])
|
50
|
+
@interface.logWatched(path)
|
51
|
+
else
|
52
|
+
@interface.watch_list(@items.values[sel["out"]])
|
53
|
+
end
|
54
|
+
elsif key == @pref.get("keybindings")["anime_set_watched"] and not empty
|
55
|
+
@interface.addWatched(path)
|
56
|
+
elsif key == @pref.get("keybindings")["anime_undo_set_watched"]
|
57
|
+
@interface.rmWatched
|
58
|
+
elsif key == @pref.get("keybindings")["anime_set_unwatched"] and not empty
|
59
|
+
@interface.rmWatched(path)
|
60
|
+
moveCursor("down")
|
61
|
+
end
|
56
62
|
|
57
63
|
changed
|
58
64
|
notify_observers
|
59
|
-
|
65
|
+
end
|
60
66
|
end
|
61
67
|
end
|
62
68
|
end
|
data/lib/aniview/view/menu.rb
CHANGED
@@ -9,18 +9,17 @@ module Aniview
|
|
9
9
|
include Aniview::Util
|
10
10
|
include Observable
|
11
11
|
|
12
|
-
attr_accessor :
|
12
|
+
attr_accessor :attributes
|
13
|
+
attr_accessor :format
|
13
14
|
|
14
15
|
def initialize(refresh_function: :items, interface:, name:, pref:, format:, term:, children: true, visible: false, logger: nil)
|
15
16
|
@attributes = {}
|
16
17
|
|
17
18
|
@logger = logger
|
18
19
|
|
19
|
-
@visible = visible
|
20
|
-
|
21
20
|
@refresh_function = refresh_function
|
22
21
|
@interface = interface
|
23
|
-
|
22
|
+
@attributes["t"] = name
|
24
23
|
@pref = pref
|
25
24
|
@format = format
|
26
25
|
@term = term
|
@@ -65,7 +64,7 @@ module Aniview
|
|
65
64
|
refresh
|
66
65
|
changed
|
67
66
|
notify_observers
|
68
|
-
sleep
|
67
|
+
sleep 1
|
69
68
|
end
|
70
69
|
end
|
71
70
|
when :stop
|
@@ -73,7 +72,7 @@ module Aniview
|
|
73
72
|
end
|
74
73
|
end
|
75
74
|
|
76
|
-
def
|
75
|
+
def rfunc=(new_method)
|
77
76
|
@refresh_function = new_method
|
78
77
|
@items_formatted = {}
|
79
78
|
@items_hash = {}
|
@@ -100,15 +99,7 @@ module Aniview
|
|
100
99
|
adjustView
|
101
100
|
draw if redraw
|
102
101
|
end
|
103
|
-
|
104
|
-
def setName(name)
|
105
|
-
@attributes["t"] = name
|
106
|
-
end
|
107
|
-
|
108
|
-
def setFormat(format_)
|
109
|
-
@format = format_
|
110
|
-
end
|
111
|
-
|
102
|
+
|
112
103
|
def refresh
|
113
104
|
@items = @interface.send(@refresh_function)
|
114
105
|
refreshFormats
|
@@ -11,8 +11,13 @@ module Aniview
|
|
11
11
|
title = @items[sel["out"]].attributes["t"]
|
12
12
|
value = @items[sel["out"]].attributes["v"]
|
13
13
|
if key == "enter"
|
14
|
-
|
15
|
-
|
14
|
+
case @interface.pref_type(path)
|
15
|
+
when "boolean"
|
16
|
+
@interface.set(path, (not @interface.get_pref(path)))
|
17
|
+
else
|
18
|
+
newval = Util.readline(@term, title + ":", value)
|
19
|
+
@interface.set(path, newval)
|
20
|
+
end
|
16
21
|
refresh
|
17
22
|
end
|
18
23
|
end
|
data/lib/application.rb
CHANGED
@@ -75,11 +75,10 @@ module Aniview
|
|
75
75
|
|
76
76
|
@aiomenu = View::AioMenu.new(
|
77
77
|
interface: @aio,
|
78
|
-
name: @pref.get("menu_titles")["
|
78
|
+
name: @pref.get("menu_titles")["library"],
|
79
79
|
pref: @pref,
|
80
|
-
format: "
|
80
|
+
format: "format_library",
|
81
81
|
term: @term,
|
82
|
-
refresh_function: :unwatched,
|
83
82
|
logger: @logger
|
84
83
|
)
|
85
84
|
@prefmenu = View::PrefMenu.new(
|
@@ -172,24 +171,28 @@ module Aniview
|
|
172
171
|
#
|
173
172
|
# @return nil
|
174
173
|
#
|
175
|
-
def
|
174
|
+
def change_view(view:, rfunc: :items, format:nil, name:nil)
|
176
175
|
@view.delete_observers
|
177
176
|
@view.pause
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
177
|
+
view.attributes["t"] = name if name
|
178
|
+
view.format = format if format
|
179
|
+
view.rfunc = rfunc
|
180
|
+
view.add_observer(self, :drawview)
|
181
|
+
view.change_screen_size redraw: false
|
182
|
+
view.expand -1
|
183
|
+
view.unpause
|
184
|
+
@view = view
|
183
185
|
end
|
184
186
|
|
185
187
|
def drawview
|
186
188
|
@view.draw
|
189
|
+
@statusline.draw
|
187
190
|
@rcount ||= 0
|
188
191
|
#puts "refreshed view #{@rcount+=1}"
|
189
192
|
end
|
190
193
|
|
191
194
|
def togglestatusline
|
192
|
-
return unless @mpvbridge.what_changed
|
195
|
+
return unless @mpvbridge.what_changed[:playing_status]
|
193
196
|
unless @statusthread
|
194
197
|
@statusthread = Thread.new do
|
195
198
|
while true
|
@@ -218,13 +221,23 @@ module Aniview
|
|
218
221
|
# @return nil
|
219
222
|
#
|
220
223
|
def run
|
224
|
+
nm = @pref.get("menu_titles")
|
225
|
+
views = {
|
226
|
+
:unwatched => {view: @aiomenu, name: nm["unwatched"], format: "format_library_unwatched", rfunc: :unwatched},
|
227
|
+
:library => {view: @aiomenu, name: nm["library"], format: "format_library"},
|
228
|
+
:torrents => {view: @delugemenu},
|
229
|
+
:preferences => {view: @prefmenu},
|
230
|
+
:schedule => {view: @schedulemenu},
|
231
|
+
:subscription => {view: @subscriptionmenu}
|
232
|
+
}
|
221
233
|
|
222
|
-
|
234
|
+
current_view = 0
|
235
|
+
@view = @aiomenu
|
236
|
+
|
223
237
|
@view.add_observer(self, :drawview)
|
224
|
-
@statusline.draw
|
225
|
-
|
226
238
|
@mpvbridge.add_observer(self, :togglestatusline)
|
227
239
|
@statusline.add_observer(self, :drawmessage)
|
240
|
+
@statusline.draw
|
228
241
|
|
229
242
|
mutex = Mutex.new
|
230
243
|
Signal.trap('SIGWINCH', proc {
|
@@ -243,31 +256,34 @@ module Aniview
|
|
243
256
|
@shoulddraw = false
|
244
257
|
|
245
258
|
kb = @pref.get("keybindings")
|
246
|
-
nm = @pref.get("menu_titles")
|
247
259
|
|
248
260
|
case key
|
249
261
|
when kb["goto_unwatched"]
|
250
|
-
|
251
|
-
|
252
|
-
@view.setFormat("format_library_unwatched")
|
253
|
-
@view.setRfunc(:unwatched)
|
262
|
+
change_view(views[:unwatched])
|
263
|
+
current_view = 0
|
254
264
|
when kb["goto_library"]
|
255
|
-
|
256
|
-
|
257
|
-
@view.setFormat("format_library")
|
258
|
-
@view.setRfunc(:items)
|
265
|
+
change_view(views[:library])
|
266
|
+
current_view = 1
|
259
267
|
when kb["goto_torrents"]
|
260
|
-
|
261
|
-
|
268
|
+
change_view(views[:torrents])
|
269
|
+
current_view = 2
|
262
270
|
when kb["goto_preferences"]
|
263
|
-
|
264
|
-
|
271
|
+
change_view(views[:preferences ])
|
272
|
+
current_view = 5
|
265
273
|
when kb["goto_schedule"]
|
266
|
-
|
267
|
-
|
274
|
+
change_view(views[:schedule])
|
275
|
+
current_view = 3
|
268
276
|
when kb["goto_subscriptions"]
|
269
|
-
|
270
|
-
|
277
|
+
change_view(views[:subscription])
|
278
|
+
current_view = 4
|
279
|
+
when "right"
|
280
|
+
current_view += 1
|
281
|
+
current_view = views.length - 1 if current_view >= views.length
|
282
|
+
change_view(views.values[current_view])
|
283
|
+
when "left"
|
284
|
+
current_view -= 1
|
285
|
+
current_view = 0 if current_view < 0
|
286
|
+
change_view(views.values[current_view])
|
271
287
|
when ":"
|
272
288
|
@view.pause
|
273
289
|
if @statusthread
|
@@ -282,6 +298,7 @@ module Aniview
|
|
282
298
|
end
|
283
299
|
@view.control(key)
|
284
300
|
end
|
301
|
+
|
285
302
|
end
|
286
303
|
end
|
287
304
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aniview
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- annacrombie
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: streamio-ffmpeg
|
@@ -156,6 +156,7 @@ files:
|
|
156
156
|
- "./lib/aniview/interface/schedule/scheduleitem.rb"
|
157
157
|
- "./lib/aniview/interface/subscription/subscription.rb"
|
158
158
|
- "./lib/aniview/util/error.rb"
|
159
|
+
- "./lib/aniview/util/folder_listen.rb"
|
159
160
|
- "./lib/aniview/util/term.rb"
|
160
161
|
- "./lib/aniview/util/util.rb"
|
161
162
|
- "./lib/aniview/view/aiomenu.rb"
|