pig-media-server 0.3.2 → 2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/bin/pig-media-server +4 -1
  4. data/gulpfile.js +33 -0
  5. data/javascripts/application.js +117 -0
  6. data/javascripts/chromecast.js +57 -0
  7. data/javascripts/components/head.js +106 -0
  8. data/javascripts/components/list.js +104 -0
  9. data/javascripts/components/new_flag.js +23 -0
  10. data/javascripts/components/player.js +171 -0
  11. data/javascripts/components/query_list.js +68 -0
  12. data/javascripts/components/sort.js +64 -0
  13. data/javascripts/components/watch.js +48 -0
  14. data/javascripts/controller.js +62 -0
  15. data/javascripts/custom_list.js +14 -0
  16. data/javascripts/event_dispatcher.js +21 -0
  17. data/javascripts/recent.js +34 -0
  18. data/javascripts/utils.js +22 -0
  19. data/javascripts/video.js +10 -0
  20. data/lib/pig-media-server/api.rb +96 -0
  21. data/lib/pig-media-server/aspect.rb +10 -5
  22. data/lib/pig-media-server/backup.rb +69 -0
  23. data/lib/pig-media-server/cli.rb +60 -25
  24. data/lib/pig-media-server/crawl.rb +41 -5
  25. data/lib/pig-media-server/kindle_send.rb +31 -24
  26. data/lib/pig-media-server/model/data.rb +106 -0
  27. data/lib/pig-media-server/model/migrate.rb +34 -0
  28. data/lib/pig-media-server/model/pig.rb +11 -2
  29. data/lib/pig-media-server/version.rb +1 -1
  30. data/lib/pig-media-server/views/_custom_links.haml +4 -0
  31. data/lib/pig-media-server/views/_ft_flv.haml +1 -0
  32. data/lib/pig-media-server/views/_ft_pdf.haml +0 -0
  33. data/lib/pig-media-server/views/_ft_video.haml +9 -4
  34. data/lib/pig-media-server/views/_link.haml +4 -1
  35. data/lib/pig-media-server/views/_new_flag.haml +1 -1
  36. data/lib/pig-media-server/views/app.scss +10 -1
  37. data/lib/pig-media-server/views/bundle.js +13 -0
  38. data/lib/pig-media-server/views/chromecast.coffee +54 -0
  39. data/lib/pig-media-server/views/config.coffee +13 -1
  40. data/lib/pig-media-server/views/config.haml +27 -4
  41. data/lib/pig-media-server/views/feed.builder +3 -2
  42. data/lib/pig-media-server/views/flv.coffee +33 -0
  43. data/lib/pig-media-server/views/index.haml +11 -5
  44. data/lib/pig-media-server/views/meta.haml +4 -4
  45. data/lib/pig-media-server/views/movie.coffee +65 -39
  46. data/lib/pig-media-server/views/react.haml +22 -0
  47. data/lib/pig-media-server/views/remote.haml +2 -2
  48. data/lib/pig-media-server/views/session.coffee +5 -0
  49. data/lib/pig-media-server/views/storage.coffee +5 -16
  50. data/lib/pig-media-server/views/sub.haml +5 -6
  51. data/lib/pig-media-server/views/subview.coffee +109 -19
  52. data/lib/pig-media-server/views/tv.coffee +97 -0
  53. data/lib/pig-media-server/views/tv.haml +33 -0
  54. data/lib/pig-media-server/views/unread.coffee +10 -0
  55. data/lib/pig-media-server/web.rb +102 -37
  56. data/package.json +23 -0
  57. data/pig-media-server.gemspec +5 -0
  58. metadata +123 -38
@@ -0,0 +1,68 @@
1
+ class Item extends React.Component {
2
+ url(){ return `/?query=${encodeURIComponent(this.props.query)}` }
3
+
4
+ click(){
5
+ this.props.state.open(this.url());
6
+ }
7
+
8
+ delete_query(){
9
+ if(confirm('Really?')){
10
+ $.post("/api/r/delete_query_list", {query: this.props.query}).done((data)=>{
11
+ this.props.update_list();
12
+ });
13
+ }
14
+ }
15
+ render(){
16
+ return <span className='main_span'>
17
+ <a href='javascript:void(0)' onClick={()=> this.click()}>
18
+ {this.props.query}
19
+ </a>
20
+ <a href='javascript:void(0)' className='delete' onClick={()=> this.delete_query()}>
21
+ Delete
22
+ </a>
23
+ </span>
24
+ }
25
+ }
26
+
27
+ class QueryList extends React.Component {
28
+ constructor(props){
29
+ super(props);
30
+ this.state = {};
31
+ this.state.list = [];
32
+ this.controller = new Controller()
33
+ }
34
+
35
+ update_list(){
36
+ $.get("/api/r/query_list").done((data)=>{
37
+ this.state.list = data;
38
+ this.setState(this.state);
39
+ });
40
+ }
41
+
42
+ componentDidMount(){
43
+ this.update_list();
44
+ }
45
+
46
+ show(){
47
+ if(location.pathname == '/'){
48
+ if(!this.controller.query()){
49
+ return true;
50
+ } else {
51
+ return false;
52
+ }
53
+ } else {
54
+ return false
55
+ }
56
+ }
57
+
58
+ render(){
59
+ var items = $.map(this.state.list, (query)=>{
60
+ return <Item key={query} query={query} state={this.props.state} update_list={()=> this.update_list()}/>
61
+ });
62
+ return <div>
63
+ {this.show() ? items : null}
64
+ </div>
65
+ }
66
+ }
67
+
68
+ window.QueryList = QueryList;
@@ -0,0 +1,64 @@
1
+ class QuerySave extends React.Component{
2
+ constructor(props){
3
+ super(props);
4
+ this.controller = new Controller();
5
+ }
6
+
7
+ click(){
8
+ var query = decodeURIComponent(this.controller.query());
9
+ $.post("/api/r/query_list", {query: query})
10
+ }
11
+ render(){
12
+ return <span>
13
+ <a href='javascript:void(0)' onClick={()=> this.click()}>Save Query</a>
14
+ </span>
15
+ }
16
+ }
17
+
18
+ class Sort extends React.Component{
19
+ constructor(props){
20
+ super(props);
21
+ this.controller = new Controller();
22
+ }
23
+
24
+ sort_by_date(){
25
+ var query = this.controller.query();
26
+ var new_url = `/?query=${query}&sort=date&order=descending`
27
+ history.pushState("", "", new_url);
28
+ this.props.state.initialize();
29
+ }
30
+
31
+ sort_by_name(){
32
+ var query = this.controller.query();
33
+ var new_url = `/?query=${query}&sort=name&order=ascending`
34
+ history.pushState("", "", new_url);
35
+ this.props.state.initialize();
36
+ }
37
+
38
+ change_order(order){
39
+ var query = this.controller.query();
40
+ var sort = this.controller.params().sort;
41
+ var new_url = `/?query=${query}&sort=${sort}&order=${order}`
42
+ history.pushState("", "", new_url);
43
+ this.props.state.initialize();
44
+
45
+ }
46
+
47
+ can_change_order(){ return !!this.controller.params().sort; }
48
+ render(){
49
+ return <p>
50
+ <a href="javascript:void(0)" onClick={()=> this.sort_by_date()}>Sort By Date</a>
51
+ <a href="javascript:void(0)" onClick={()=> this.sort_by_name()}>Sort By Name</a>
52
+ {this.can_change_order() ?
53
+ <span>
54
+ <a href="javascript:void(0)" onClick={()=> this.change_order('ascending')}>Ascending</a>
55
+ <a href="javascript:void(0)" onClick={()=> this.change_order('descending')}>Descending</a>
56
+ </span>
57
+ : null
58
+ }
59
+ <QuerySave />
60
+ </p>
61
+ }
62
+ }
63
+
64
+ window.Sort = Sort;
@@ -0,0 +1,48 @@
1
+ class ChromeCast extends React.Component {
2
+ is_video(){ var e = ext(this.props.item.name); return e == 'mp4' || e == 'flv'; }
3
+
4
+ click(){ window.chrome_cast(this.props.item.url, this.props.item.key) }
5
+ render(){
6
+ return <span>
7
+ {this.is_video() ?
8
+ <span>
9
+ <a
10
+ className="watch"
11
+ href="javascript:void(0)"
12
+ title={this.props.item.name}
13
+ onClick={()=> this.click()}
14
+ >ChromeCast</a>
15
+ &nbsp;
16
+ </span>
17
+ : null}
18
+ </span>
19
+ }
20
+ }
21
+
22
+
23
+ class Watch extends React.Component {
24
+ is_video(){
25
+ var e = ext(this.props.item.name);
26
+ return e == 'mp4' || e == 'flv';
27
+ }
28
+
29
+ set_video(){ this.props.state.models.video.set(this.props.item); }
30
+ render(){
31
+ return <span>
32
+ {this.is_video() ?
33
+ <span>
34
+ <a
35
+ className="watch"
36
+ href="javascript:void(0)"
37
+ title={this.props.item.name}
38
+ onClick={()=> this.set_video()}
39
+ >Watch</a>
40
+ &nbsp;
41
+ </span>
42
+ : null}
43
+ </span>
44
+ }
45
+ }
46
+
47
+ window.Watch = Watch;
48
+ window.ChromeCast = ChromeCast;
@@ -0,0 +1,62 @@
1
+ class Controller {
2
+ route(){
3
+ var query =this.params().query;
4
+ var result = {}
5
+ switch(location.pathname){
6
+ case "/":
7
+ result.page = "list";
8
+ if(!!query){
9
+ result.api_url = `/api/r/search?query=${query}`
10
+ if(!!this.params().sort){ result.api_url += `&sort=${this.params().sort}` }
11
+ if(!!this.params().order){ result.api_url += `&order=${this.params().order}` }
12
+ if(!!this.params().page){ result.api_url += `&page=${this.params().page}` }
13
+ $('title').text(decodeURIComponent(this.query()));
14
+ } else {
15
+ result.api_url = null;
16
+ }
17
+ break;
18
+
19
+ case "/latest":
20
+ result.page = "list";
21
+ result.api_url = '/api/r/latest';
22
+ $('title').text("Latest - Pig Media Server");
23
+ break;
24
+ case "/custom":
25
+ result.page = "list";
26
+ result.api_url = `/api/r/custom?name=${this.params().name}`;
27
+ $('title').text(decodeURIComponent(this.params().name));
28
+ break;
29
+
30
+ case "/recommend":
31
+ result.page = "list";
32
+ result.api_url = `/api/r/recommend?name=${this.params().name}`;
33
+ $('title').text('Recommend');
34
+ break;
35
+ }
36
+ return result;
37
+ }
38
+
39
+
40
+
41
+ params(){
42
+ var arg = new Object;
43
+ var pair = location.search.substring(1).split('&');
44
+ for(var i=0; pair[i]; i++) {
45
+ var kv = pair[i].split('=');
46
+ arg[kv[0]] = kv[1];
47
+ }
48
+ return arg;
49
+ }
50
+
51
+ query(){ return this.params().query; }
52
+
53
+ can_sort_and_paging(){
54
+ if(location.pathname == '/'){
55
+ return !!this.query();
56
+ } else {
57
+ return false;
58
+ }
59
+ }
60
+ }
61
+
62
+ window.Controller = Controller;
@@ -0,0 +1,14 @@
1
+ class CustomList {
2
+ constructor(app){
3
+ this.app = app;
4
+ this.list = []
5
+ }
6
+ load(){
7
+ $.get('/api/r/custom_list').done((data)=>{
8
+ this.list = data;
9
+ this.app.update_state();
10
+ });
11
+ }
12
+ }
13
+
14
+ window.CustomList = CustomList;
@@ -0,0 +1,21 @@
1
+ class EventDispatcher {
2
+ constructor(){
3
+ this.listeners = {};
4
+ }
5
+
6
+ addEventListener(type, callback){
7
+ if (!this.listeners[type]) { this.listeners[type] = []; }
8
+ this.listeners[type].push(callback);
9
+ }
10
+ clearEventListener(){ this.listeners = {}; }
11
+
12
+ dispatchEvent(event) {
13
+ if (this.listeners[event.type]) {
14
+ for (var listener in this.listeners[event.type]) {
15
+ this.listeners[event.type][listener].apply(this.listeners, arguments);
16
+ }
17
+ }
18
+ }
19
+ }
20
+
21
+ window.EventDispatcher = EventDispatcher;
@@ -0,0 +1,34 @@
1
+ class Recent {
2
+ constructor(app){
3
+ this.app = app;
4
+ }
5
+
6
+ login(){ return !!this.app.state.session.user_id }
7
+
8
+ load(){
9
+ if(this.login()){
10
+ var dd = new Date();
11
+ $.get('/recents', {stamp: dd.getTime()}).done((data)=>{
12
+ this.app.state.recents = data;
13
+ this.app.update_state();
14
+ });
15
+ } else {
16
+ }
17
+ }
18
+
19
+ use(key){
20
+ if(this.login()){
21
+ var dd = new Date();
22
+ $.get('/recents', {stamp: dd.getTime()}).done((recents)=>{
23
+ recents[key] = {time: parseInt((new Date)/1000), type: 'movie'}
24
+ $.post("/recents", {data: JSON.stringify(recents)});
25
+ this.app.state.recents = recents;
26
+ this.app.update_state();
27
+ });
28
+ } else {
29
+ console.log("kotti");
30
+ }
31
+ }
32
+ }
33
+
34
+ window.Recent = Recent;
@@ -0,0 +1,22 @@
1
+ window.round = (i)=>{
2
+ i = i*10;
3
+ return Math.round(i)/10
4
+ }
5
+
6
+ window.size_pretty = (size)=>{
7
+ var size = parseFloat(String(size));
8
+ var result = size;
9
+ if(size < 1024){
10
+ result = `${size} Bytes`
11
+ } else if(size < 1024*1024){
12
+ result = `${round(size / 1024)} KB`
13
+ } else if(size < 1024*1024*1024){
14
+ result = `${round(size / (1024*1024))} MB`
15
+ } else if(size < 1024*1024*1024*1024){
16
+ result = `${round(size / (1024*1024*1024))} GB`
17
+ }
18
+ return result
19
+ }
20
+
21
+
22
+ window.ext = (str)=>{ return String(str).split('.').pop().toLowerCase(); }
@@ -0,0 +1,10 @@
1
+ class Video extends EventDispatcher{
2
+ set(item){
3
+ this.item = null;
4
+ this.dispatchEvent({type: 'videoUpdated'});
5
+ this.item = item;
6
+ this.dispatchEvent({type: 'videoUpdated'});
7
+ }
8
+ }
9
+
10
+ window.Video = Video;
@@ -0,0 +1,96 @@
1
+ require 'active_support/concern'
2
+ module PigMediaServer
3
+ module API
4
+ extend ActiveSupport::Concern
5
+ def page
6
+ params[:page].to_i < 1 ? 1 : params[:page].to_i
7
+ end
8
+
9
+ def size
10
+ params[:size] ? params[:size].to_i : 50
11
+ end
12
+
13
+ def list_to_json list
14
+ list.map{|x|
15
+ hash = x.to_hash
16
+ hash['custom_links'] = partial :_custom_links, locals: {record: x}
17
+ hash['metadata'] = !!x.metadata and x.metadata != ''
18
+ hash['srt'] = !!x.metadata and x.metadata != ''
19
+ hash
20
+ }.to_json
21
+ end
22
+
23
+ included do
24
+ get '/api/r/latest' do
25
+ content_type :json
26
+ list_to_json(Groonga['Files'].select.paginate([key: 'mtime', order: 'descending'], size: size, page: page).map{|x| Pig.new(x)})
27
+ end
28
+
29
+ get '/api/r/custom' do
30
+ content_type :json
31
+ c = config['custom_list'][params[:name]]
32
+ list_to_json(Pig.find JSON.parse(open(c).read))
33
+ end
34
+
35
+ get '/api/r/recommend' do
36
+ content_type :json
37
+ list = []
38
+ begin
39
+ keys = open("#{config['user_data_path']}/recommend/#{params[:name]}").read.split("\n")
40
+ list = Pig.find(keys)
41
+ rescue
42
+ end
43
+ list_to_json(list)
44
+ end
45
+
46
+ get '/api/r/search' do
47
+ content_type :json
48
+ list_to_json(Pig.search params.merge(page: page))
49
+ end
50
+
51
+ get '/api/r/config' do
52
+ content_type :json
53
+ config.to_json
54
+ end
55
+
56
+ get '/api/r/session' do
57
+ content_type :json
58
+ session.to_hash.to_json
59
+ end
60
+
61
+ get '/api/r/custom_list' do
62
+ content_type :json
63
+ if config['custom_list']
64
+ config['custom_list'].to_json
65
+ else
66
+ {}.to_json
67
+ end
68
+ end
69
+
70
+ get '/api/r/query_list' do
71
+ content_type :json
72
+ Groonga['QueryList'].select.map{|x| x.query }.sort.to_json
73
+ end
74
+
75
+ post '/api/r/query_list' do
76
+ content_type :json
77
+ key = Digest::MD5.hexdigest(params[:query])
78
+ unless Groonga['QueryList'][key]
79
+ Groonga['QueryList'].add(key)
80
+ Groonga['QueryList'][key].query = params[:query]
81
+ end
82
+ {result: true}.to_json
83
+ end
84
+
85
+ post '/api/r/delete_query_list' do
86
+ content_type :json
87
+ key = Digest::MD5.hexdigest(params[:query])
88
+ if Groonga['QueryList'][key]
89
+ Groonga['QueryList'].delete key
90
+ end
91
+ {result: true}.to_json
92
+
93
+ end
94
+ end
95
+ end
96
+ end
@@ -7,22 +7,27 @@ module PigMediaServer
7
7
  pit_config = Pit.get 'Pig Media Server'
8
8
  while true
9
9
  GC.start
10
- begin
10
+ #begin
11
11
  q = open("#{pit_config['user_data_path']}/rate/queue.txt").read.split("\n")
12
12
  q.each do |x|
13
- next if x == ''
13
+ #begin
14
+ #next if x == ''
14
15
  key = x.split(' ').first
15
16
  rate = x.split(' ').last
16
17
  pig = Pig.find key
18
+ next unless pig
19
+ puts pig.path
17
20
  if rate == '16:9'
18
21
  system 'MP4Box', '-add', "#{pig.record.path}:par=1:1", '-new', "#{pig.record.path}.MP4"
19
22
  FileUtils.mv "#{pig.record.path}.MP4", pig.record.path
20
23
  end
24
+ #rescue
25
+ #end
21
26
  end
22
27
  open("#{pit_config['user_data_path']}/rate/queue.txt", 'w'){|x| x.puts ''}
23
- rescue => e
24
- p e
25
- end
28
+ #rescue => e
29
+ # p e
30
+ #end
26
31
  sleep 5
27
32
  end
28
33
  end
@@ -0,0 +1,69 @@
1
+ require 'pig-media-server/script'
2
+ require 'tmpdir'
3
+
4
+ module PigMediaServer
5
+ class Backup
6
+ def backup
7
+ pit_config = Pit.get 'Pig Media Server'
8
+ backup_path = pit_config['backup_path']
9
+ Dir.mktmpdir do |tmpdir|
10
+ FileUtils.cd tmpdir
11
+ date = Date.today
12
+
13
+ a = $f.select.to_a
14
+ open("backup.json", "w"){|f|
15
+ a.each_with_index{|x,i|
16
+ f.puts [x._key, x.path, x.metadata, x.srt].to_json
17
+ puts "#{i+1} / #{a.count}" if i%100 ==0
18
+ }
19
+ }
20
+ open('groonga.schema', 'w'){|x| x.puts Groonga::Schema.dump}
21
+ system "tar zcvf #{date}.tar.gz backup.json groonga.schema"
22
+ FileUtils.mv "#{date}.tar.gz", backup_path
23
+ end
24
+ end
25
+
26
+ def restore_from path
27
+ raise unless File.exist? path
28
+ pit_config = Pit.get 'Pig Media Server'
29
+ Dir.glob("#{pit_config['groonga']}/**/*").each{|x| FileUtils.rm x}
30
+ Groonga::Database.create path: pit_config['groonga']+"/search"
31
+ Dir.mktmpdir do |tmpdir|
32
+ FileUtils.cd tmpdir
33
+ FileUtils.cp path, "."
34
+ system 'tar', 'xvf', Dir.glob("*.gz").first
35
+ system 'ls'
36
+ FileUtils.rm Dir.glob("*.gz").first
37
+ Groonga::Schema.restore(open('groonga.schema').read){|schema| schema.define }
38
+ $f = Groonga['Files']
39
+ json =open("backup.json").read.split("\n")
40
+ json.each_with_index{|x,i|
41
+ x = JSON.parse x
42
+ $f.add(x[0])
43
+ $f[x[0]].path = x[1]
44
+ $f[x[0]].metadata = x[2]
45
+ $f[x[0]].srt = x[3]
46
+ puts "#{i+1} / #{json.count}" if i%100 == 0 or i+1 == json.count
47
+ }
48
+ end
49
+ ary = $f.select.to_a
50
+
51
+ lost = []
52
+
53
+ ary.each_with_index{|x,i|
54
+ if x.path.nil?
55
+ $f.delete x._key
56
+ next
57
+ end
58
+ unless File.exist? x.path
59
+ puts x.path
60
+ $f.delete x._key
61
+ next
62
+ end
63
+ x.mtime = File::Stat.new(x.path).mtime.to_i
64
+ puts "#{i+1} / #{ary.count}" if (i+1)%1000 == 0 or i+1 == ary.count
65
+ }
66
+ lost.sort.each{|x| puts x}
67
+ end
68
+ end
69
+ end
@@ -1,27 +1,62 @@
1
- case ARGV[0]
2
- when 'setup'
3
- require 'fileutils'
4
- config = Pit.get("Pig Media Server", require:{
5
- 'path' => 'Path of your storage',
6
- 'groonga' => "Path of groonga's files",
7
- 'exclude_path' => 'Exclude Path(Array)',
8
- 'user_data_path' => 'Path of User Data'
9
- })
1
+ require 'thor'
10
2
 
11
- puts "Path: #{config['path']}"
12
- puts "Groonga: #{config['groonga']}"
13
- FileUtils.mkdir_p config['user_data_path']
14
- when 'crawl'
15
- require 'pig-media-server/crawl'
16
- PigMediaServer::Crawl.new.all
17
- when 'server'
18
- port = ARGV[1] ? ARGV[1].to_i : 8080
19
- require 'pig-media-server/web'
20
- PigMediaServer::Web.run! host: '0.0.0.0', port: port
21
- when 'aspect'
22
- require 'pig-media-server/aspect'
23
- PigMediaServer::Aspect.new.run
24
- when 'kindle-send'
25
- require 'pig-media-server/kindle_send'
26
- PigMediaServer::KindleSend.new.run
3
+ module PigMediaServer
4
+ class CLI < Thor
5
+ desc 'setup', 'setup configurations'
6
+ def setup
7
+ require 'fileutils'
8
+ config = Pit.get("Pig Media Server", require:{
9
+ 'hostname' => 'Your host name',
10
+ 'path' => 'Path of your storage',
11
+ 'groonga' => "Path of groonga's files",
12
+ 'exclude_path' => 'Exclude Path(Array)',
13
+ 'user_data_path' => 'Path of User Data'
14
+ })
15
+
16
+ puts "Path: #{config['path']}"
17
+ puts "Groonga: #{config['groonga']}"
18
+ FileUtils.mkdir_p config['user_data_path']
19
+ end
20
+
21
+ desc 'crawl', 'run crawler'
22
+ def crawl
23
+ require 'pig-media-server/crawl'
24
+ PigMediaServer::Crawl.new.all
25
+ end
26
+
27
+ desc 'server [PORT]', 'run server'
28
+ option :bind, :type => :string, :default => '0.0.0.0'
29
+ option :port, :type => :numeric, :default => 8080
30
+ def server(port = nil)
31
+ require 'pig-media-server/web'
32
+ port ||= options[:port]
33
+ Sinatra::Base.server.delete 'HTTP' # conflict with HTTP.gem
34
+ PigMediaServer::Web.run! :bind => options[:bind], :port => port.to_i
35
+ end
36
+
37
+ desc 'aspect', 'fix aspect'
38
+ def aspect
39
+ require 'pig-media-server/aspect'
40
+ PigMediaServer::Aspect.new.run
41
+ end
42
+
43
+ desc 'kindle-send', 'send to kindle'
44
+ def kindle_send
45
+ require 'pig-media-server/kindle_send'
46
+ PigMediaServer::KindleSend.new.run
47
+ end
48
+
49
+ desc 'backup', 'backup groonga\'s data'
50
+ def backup
51
+ require 'pig-media-server/backup'
52
+ PigMediaServer::Backup.new.backup
53
+ end
54
+
55
+ option :path, required: true
56
+ desc 'restore', 'restore groonga\'s data'
57
+ def restore
58
+ require 'pig-media-server/backup'
59
+ PigMediaServer::Backup.new.restore_from options[:path]
60
+ end
61
+ end
27
62
  end