pastehub 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +32 -0
- data/Rakefile +146 -0
- data/VERSION.yml +4 -0
- data/bin/PastehubSync +107 -0
- data/bin/pastehubDump +114 -0
- data/bin/pastehubPost +63 -0
- data/lib/pastehub.rb +43 -0
- data/lib/pastehub/auth.rb +171 -0
- data/lib/pastehub/client.rb +453 -0
- data/lib/pastehub/clientsync.rb +287 -0
- data/lib/pastehub/clipboard.rb +56 -0
- data/lib/pastehub/config.rb +166 -0
- data/lib/pastehub/crypt.rb +70 -0
- data/lib/pastehub/localdb.rb +187 -0
- data/lib/pastehub/log.rb +95 -0
- data/lib/pastehub/macosx.rb +66 -0
- data/lib/pastehub/masterdb.rb +213 -0
- data/lib/pastehub/store.rb +83 -0
- data/lib/pastehub/util.rb +153 -0
- data/server/masterdb.rb +141 -0
- data/server/notifier.rb +89 -0
- metadata +235 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
#
|
2
|
+
# store.rb - PasteHub's storage sync library
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009-2011 Kiyoka Nishiyama <kiyoka@sumibi.org>
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions
|
8
|
+
# are met:
|
9
|
+
#
|
10
|
+
# 1. Redistributions of source code must retain the above copyright
|
11
|
+
# notice, this list of conditions and the following disclaimer.
|
12
|
+
#
|
13
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
14
|
+
# notice, this list of conditions and the following disclaimer in the
|
15
|
+
# documentation and/or other materials provided with the distribution.
|
16
|
+
#
|
17
|
+
# 3. Neither the name of the authors nor the names of its contributors
|
18
|
+
# may be used to endorse or promote products derived from this
|
19
|
+
# software without specific prior written permission.
|
20
|
+
#
|
21
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
22
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
23
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
24
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
25
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
26
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
27
|
+
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
28
|
+
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
29
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
30
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
31
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
#
|
33
|
+
#
|
34
|
+
|
35
|
+
module PasteHub
|
36
|
+
|
37
|
+
class LocalStore
|
38
|
+
def initialize( username, reader = false )
|
39
|
+
@db = PasteHub::LocalDB.new( PasteHub::Config.instance.localDbPath )
|
40
|
+
@db.open( username, reader )
|
41
|
+
end
|
42
|
+
|
43
|
+
def getList
|
44
|
+
@db.getList()
|
45
|
+
end
|
46
|
+
|
47
|
+
def getServerList
|
48
|
+
@db.getServerList()
|
49
|
+
end
|
50
|
+
|
51
|
+
def top
|
52
|
+
lst = @db.getList( 1 )
|
53
|
+
if 0 < lst.size
|
54
|
+
[ lst.first, @db.getValue( lst.first ) ]
|
55
|
+
else
|
56
|
+
[ nil, nil ]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def getValue( key )
|
61
|
+
@db.getValue( key.dup )
|
62
|
+
end
|
63
|
+
|
64
|
+
def latest
|
65
|
+
[ @db.getValue( PasteHub::SERVER_DATE_KEY ).to_s,
|
66
|
+
@db.getValue( PasteHub::LOCAL_DATE_KEY ).to_s ]
|
67
|
+
end
|
68
|
+
|
69
|
+
def insertValue( key, value )
|
70
|
+
@db.insertValue( key, value )
|
71
|
+
end
|
72
|
+
|
73
|
+
def close
|
74
|
+
@db.close
|
75
|
+
end
|
76
|
+
|
77
|
+
def clear
|
78
|
+
@db.clear
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'date'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module PasteHub
|
6
|
+
require 'rubygems'
|
7
|
+
begin
|
8
|
+
require 'highline'
|
9
|
+
USE_HIGHLINE = true
|
10
|
+
rescue LoadError
|
11
|
+
USE_HIGHLINE = false
|
12
|
+
end
|
13
|
+
|
14
|
+
class Util
|
15
|
+
def initialize()
|
16
|
+
end
|
17
|
+
|
18
|
+
# return message digest for str.
|
19
|
+
def digest( str )
|
20
|
+
Digest::SHA1.hexdigest( str )
|
21
|
+
end
|
22
|
+
|
23
|
+
# return the currentTime in Unixtime
|
24
|
+
def currentTime( )
|
25
|
+
dt = Time.new.gmtime.to_datetime()
|
26
|
+
currentDate = dt.strftime( "%s" ) + "=" + dt.strftime( "%F.%H:%M:%S" )
|
27
|
+
currentDate
|
28
|
+
end
|
29
|
+
|
30
|
+
def currentSeconds( )
|
31
|
+
self.key_seconds( self.currentTime() )
|
32
|
+
end
|
33
|
+
|
34
|
+
def _splitKey( key )
|
35
|
+
key.split( /[=]/ )
|
36
|
+
end
|
37
|
+
|
38
|
+
def key_seconds( key )
|
39
|
+
arr = _splitKey( key )
|
40
|
+
if 1 < arr.size
|
41
|
+
arr[0].to_i
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def key_timestamp( key )
|
48
|
+
arr = _splitKey( key )
|
49
|
+
if 1 < arr.size
|
50
|
+
arr[1]
|
51
|
+
else
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def key_digest( key )
|
57
|
+
arr = _splitKey( key )
|
58
|
+
if 2 < arr.size
|
59
|
+
arr[2]
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def diffList( list1, list2 )
|
66
|
+
set1 = Set.new
|
67
|
+
set2 = Set.new
|
68
|
+
list1.each { |e| set1.add(e) }
|
69
|
+
list2.each { |e| set2.add(e) }
|
70
|
+
set1.difference( set2 ).to_a
|
71
|
+
end
|
72
|
+
|
73
|
+
# Same as Gauche's take* function
|
74
|
+
def takeList( list1, num )
|
75
|
+
if ( num < 0 )
|
76
|
+
list1
|
77
|
+
elsif num <= list1.size
|
78
|
+
list1[ 0 ... num ]
|
79
|
+
else
|
80
|
+
list1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Same as Gauche's drop* function
|
85
|
+
def dropList( list1, num )
|
86
|
+
if num < 0
|
87
|
+
list1
|
88
|
+
elsif num <= list1.size
|
89
|
+
list1[ num .. list1.size ]
|
90
|
+
else
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def say( message )
|
96
|
+
if USE_HIGHLINE
|
97
|
+
HighLine.new.say( message ) {|q|
|
98
|
+
q.readline = true
|
99
|
+
}
|
100
|
+
else
|
101
|
+
puts "#{message}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def inputText( label )
|
106
|
+
if USE_HIGHLINE
|
107
|
+
HighLine.new.ask( label ) {|q|
|
108
|
+
q.readline = true
|
109
|
+
}
|
110
|
+
else
|
111
|
+
print "#{label}"
|
112
|
+
return gets.chomp
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def inputPassword( label )
|
117
|
+
if USE_HIGHLINE
|
118
|
+
HighLine.new.ask( label ) {|q|
|
119
|
+
q.readline = true
|
120
|
+
q.echo = '*'
|
121
|
+
}
|
122
|
+
else
|
123
|
+
print "#{label}"
|
124
|
+
return gets.chomp
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# input utility
|
129
|
+
def inputPasswordTwice( message, firstLabel, secondLabel )
|
130
|
+
required_password_chars = 6
|
131
|
+
|
132
|
+
STDERR.puts( message )
|
133
|
+
3.times { |n|
|
134
|
+
firstStr = nil
|
135
|
+
while not firstStr
|
136
|
+
firstStr = inputPassword(firstLabel)
|
137
|
+
if required_password_chars > firstStr.size()
|
138
|
+
STDERR.puts( "you must input #{required_password_chars} or more characters." )
|
139
|
+
firstStr = nil
|
140
|
+
elsif firstStr.match( /[ \t]/i )
|
141
|
+
STDERR.puts( "you must not use white space characters." )
|
142
|
+
firstStr = nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
secondStr = inputPassword(secondLabel)
|
146
|
+
if firstStr == secondStr
|
147
|
+
return firstStr
|
148
|
+
end
|
149
|
+
}
|
150
|
+
return nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/server/masterdb.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'vertx'
|
2
|
+
require 'json/pure'
|
3
|
+
require 'cgi'
|
4
|
+
require 'date'
|
5
|
+
require 'memcache'
|
6
|
+
|
7
|
+
$LOAD_PATH.push( File.dirname(__FILE__) + "/../lib" )
|
8
|
+
require 'pastehub'
|
9
|
+
require 'pastehub/log'
|
10
|
+
PasteHub::Config.instance.loadServer
|
11
|
+
|
12
|
+
# display config info
|
13
|
+
ins = PasteHub::Config.instance
|
14
|
+
printf( "Use AWS: %s\n", ins.aws )
|
15
|
+
printf( "Domain: %s\n", ins.domain )
|
16
|
+
printf( "Dynamo Endpoint: %s\n", ins.dynamoEp )
|
17
|
+
printf( "Memcache Endpoint: %s\n", ins.memcacheEp )
|
18
|
+
|
19
|
+
# initialize master database
|
20
|
+
require 'pastehub/masterdb'
|
21
|
+
|
22
|
+
|
23
|
+
# setup user table for Fake DynamoDB
|
24
|
+
users = PasteHub::Users.new( )
|
25
|
+
if not ins.aws
|
26
|
+
open( "/var/pastehub/users.tsv", "r" ) {|f|
|
27
|
+
f.readlines.each { |line|
|
28
|
+
pair = line.chomp.split( /[\t ]+/ )
|
29
|
+
printf( "Added local user table: %s\n", pair[0] )
|
30
|
+
users.addUser( pair[0], pair[1] )
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
notifyHash = Memcache.new( :server => PasteHub::Config.instance.memcacheEp )
|
37
|
+
|
38
|
+
masterdb_server = Vertx::HttpServer.new
|
39
|
+
masterdb_server.request_handler do |req|
|
40
|
+
|
41
|
+
req.body_handler do |body|
|
42
|
+
util = PasteHub::Util.new
|
43
|
+
auth = PasteHub::AuthForServer.new( users )
|
44
|
+
ret = auth.invoke( req.headers, util.currentSeconds() )
|
45
|
+
username = ret[1]
|
46
|
+
|
47
|
+
log = PasteHub::Log.new( :api => req.path, :user => username )
|
48
|
+
if ret[0]
|
49
|
+
log.info( "connected" )
|
50
|
+
else
|
51
|
+
log.error( 'Auth failure:' + ret[2].to_s, { :reason => ret[2].to_s } )
|
52
|
+
req.response.status_code = 403
|
53
|
+
req.response.status_message = "Authorization failure."
|
54
|
+
req.response.end
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
entries = PasteHub::Entries.new( username )
|
59
|
+
users = PasteHub::Users.new( )
|
60
|
+
|
61
|
+
case req.path
|
62
|
+
when "/authTest"
|
63
|
+
req.response.end
|
64
|
+
|
65
|
+
when "/putValue"
|
66
|
+
data = body.to_s.dup
|
67
|
+
digest = util.digest( data )
|
68
|
+
|
69
|
+
# update db
|
70
|
+
key = req.headers[ 'X-Pastehub-Key' ].dup
|
71
|
+
puts "[#{username}]:putValue: key=[#{key}] "
|
72
|
+
|
73
|
+
# client have no specified key
|
74
|
+
if "_" == key
|
75
|
+
key = util.currentTime( ) + "=" + digest
|
76
|
+
end
|
77
|
+
|
78
|
+
# data duplicate check
|
79
|
+
insertFlag = true
|
80
|
+
prevKey = notifyHash.get( username )
|
81
|
+
if prevKey
|
82
|
+
if util.key_digest( prevKey ) == digest
|
83
|
+
log.info( "canceled because data is duplicate. ", { :key => key } )
|
84
|
+
insertFlag = false
|
85
|
+
key = prevKey
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if insertFlag
|
90
|
+
Vertx.set_timer(1000) do
|
91
|
+
log.info( "START: delayed job." )
|
92
|
+
# update db
|
93
|
+
log.info( "insert", { :key => key } )
|
94
|
+
entries.insertValue( key, data )
|
95
|
+
users.touch( username )
|
96
|
+
# notify to client
|
97
|
+
notifyHash.set( username, key, PasteHub::Config.instance.keyCacheTime )
|
98
|
+
|
99
|
+
# remove Last entry
|
100
|
+
arr = entries.getList( )
|
101
|
+
if PasteHub::Config.instance.listItems < arr.size
|
102
|
+
entries.deleteValue( arr[arr.size-1] )
|
103
|
+
end
|
104
|
+
log.info( "END: delayed job", { :entries => arr.size } )
|
105
|
+
end
|
106
|
+
end
|
107
|
+
req.response.end( key )
|
108
|
+
|
109
|
+
|
110
|
+
when "/getList"
|
111
|
+
limit = req.headers[ 'X-Pastehub-Limit' ]
|
112
|
+
if limit
|
113
|
+
str = entries.getList( ).take( limit.to_i ).join( "\n" )
|
114
|
+
else
|
115
|
+
str = entries.getList( ).join( "\n" )
|
116
|
+
end
|
117
|
+
log.info( "getList", { :limit => limit, :entries => entries.getList( ).size } )
|
118
|
+
puts str
|
119
|
+
req.response.end( str )
|
120
|
+
|
121
|
+
when "/getValue"
|
122
|
+
k = body.to_s.chomp
|
123
|
+
if 0 < k.size
|
124
|
+
str = entries.getValue( k.dup )
|
125
|
+
else
|
126
|
+
str = ""
|
127
|
+
end
|
128
|
+
log.info( "getValue", { :key => k } )
|
129
|
+
puts "[#{username}]:getValue:" + k
|
130
|
+
req.response.end( str )
|
131
|
+
|
132
|
+
else
|
133
|
+
mes = "Error: Unknown API #{req.path}"
|
134
|
+
log.error( mes )
|
135
|
+
req.response.status_code = 400
|
136
|
+
req.response.status_message = mes
|
137
|
+
req.response.end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end.listen(8000)
|
data/server/notifier.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'vertx'
|
2
|
+
require 'cgi'
|
3
|
+
require 'memcache'
|
4
|
+
require 'pp'
|
5
|
+
$LOAD_PATH.push( File.dirname(__FILE__) + "/../lib" )
|
6
|
+
require 'pastehub'
|
7
|
+
require 'pastehub/log'
|
8
|
+
PasteHub::Config.instance.loadServer
|
9
|
+
|
10
|
+
# display config info
|
11
|
+
ins = PasteHub::Config.instance
|
12
|
+
printf( "Use AWS: %s\n", ins.aws )
|
13
|
+
printf( "Domain: %s\n", ins.domain )
|
14
|
+
printf( "Dynamo Endpoint: %s\n", ins.dynamoEp )
|
15
|
+
printf( "Memcache Endpoint: %s\n", ins.memcacheEp )
|
16
|
+
printf( "ssl keystore file: %s\n", ins.keystore )
|
17
|
+
|
18
|
+
# initialize master database
|
19
|
+
require 'pastehub/masterdb'
|
20
|
+
|
21
|
+
# setup user table for Fake DynamoDB
|
22
|
+
users = PasteHub::Users.new( )
|
23
|
+
if not ins.aws
|
24
|
+
open( "/var/pastehub/users.tsv", "r" ) {|f|
|
25
|
+
f.readlines.each { |line|
|
26
|
+
pair = line.chomp.split( /[\t ]+/ )
|
27
|
+
printf( "Added local user table: %s\n", pair[0] )
|
28
|
+
users.addUser( pair[0], pair[1] )
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
INTERVAL = 0.5
|
35
|
+
POLLING_SEC = 60
|
36
|
+
|
37
|
+
notifyHash = Memcache.new( :server => PasteHub::Config.instance.memcacheEp )
|
38
|
+
|
39
|
+
def notify( res, str )
|
40
|
+
res.write_str( "#{str}\n" )
|
41
|
+
end
|
42
|
+
|
43
|
+
notifier = Vertx::HttpServer.new
|
44
|
+
if ins.keystore
|
45
|
+
notifier.ssl = true
|
46
|
+
notifier.key_store_path = ins.keystore
|
47
|
+
notifier.key_store_password = ins.keystorePassword
|
48
|
+
end
|
49
|
+
|
50
|
+
notifier.request_handler do |req|
|
51
|
+
util = PasteHub::Util.new
|
52
|
+
auth = PasteHub::AuthForServer.new( users )
|
53
|
+
|
54
|
+
ret = auth.invoke( req.headers, util.currentSeconds() )
|
55
|
+
username = ret[1]
|
56
|
+
# Now send back a response
|
57
|
+
req.response.chunked = true
|
58
|
+
|
59
|
+
log = PasteHub::Log.new( :api => 'notifier', :user => username )
|
60
|
+
if ret[0]
|
61
|
+
log.info( "connected" )
|
62
|
+
else
|
63
|
+
log.error( 'Auth failure:' + ret[2].to_s, { :reason => ret[2].to_s } )
|
64
|
+
req.response.status_code = 403
|
65
|
+
req.response.status_message = "Authorization failure."
|
66
|
+
req.response.end
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
got = nil
|
71
|
+
timer_count = 0
|
72
|
+
|
73
|
+
Vertx::set_periodic (1000 * INTERVAL) { |timer_id|
|
74
|
+
timer_count += INTERVAL
|
75
|
+
if notifyHash[ username ]
|
76
|
+
if got != notifyHash[ username ]
|
77
|
+
got = notifyHash[ username ]
|
78
|
+
log.info( 'notify', { :notify => got } )
|
79
|
+
notify( req.response, got )
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if ( POLLING_SEC < timer_count )
|
84
|
+
log.info( 'timeout', { :notify => nil } )
|
85
|
+
req.response.end
|
86
|
+
Vertx::cancel_timer(timer_id)
|
87
|
+
end
|
88
|
+
}
|
89
|
+
end.listen(8001)
|