ibg 0.0.3

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.
data/lib/ibg/web.rb ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ require 'haml'
4
+ require 'sinatra/base'
5
+ require 'nkf'
6
+ require 'fileutils'
7
+ require 'base64'
8
+ require 'json'
9
+
10
+ module Iceberg
11
+
12
+ class WebApp < Sinatra::Base
13
+
14
+ ICEBERG_HOME = File.dirname(__FILE__) + '/../../'
15
+ set :protection, :except => :frame_options
16
+ set :public_folder, ICEBERG_HOME + 'public'
17
+ set :views, ICEBERG_HOME + 'views'
18
+
19
+ FORBIDDEN_CHARS = " #<>:\\/*?\"|&',;`"
20
+
21
+ enable :sessions
22
+
23
+ helpers do
24
+
25
+ include Rack::Utils; alias_method :h, :escape_html
26
+ def partial(template, options = {})
27
+ options = options.merge({:layout => false})
28
+ template = "_#{template.to_s}".to_sym
29
+ haml(template, options)
30
+ end
31
+
32
+ end
33
+
34
+ CONTENT_TYPES = {
35
+ :html => 'text/html',
36
+ :css => 'text/css',
37
+ :js => 'application/javascript',
38
+ :txt => 'text/plain',
39
+ }
40
+
41
+ before do
42
+ request_uri = case request.env['REQUEST_URI'].split('?')[0]
43
+ when /\.css$/ ; :css
44
+ when /\.js$/ ; :js
45
+ when /\.txt$/ ; :txt
46
+ else :html
47
+ end
48
+ content_type CONTENT_TYPES[request_uri], :charset => 'utf-8'
49
+ response.headers['Cache-Control'] = 'no-cache'
50
+ end
51
+
52
+ get '/' do
53
+ filemax = SETTING['local']['filemax']
54
+ recentfiles = REDIS.lrange(IBDB_RECENT, 0, filemax)
55
+ tripcodelist = REDIS.smembers(IBDB_TRIPCODE_SET)
56
+ uploaded = session[:uploaded]
57
+ session[:uploaded] = nil
58
+ maxfilesize = SETTING['local']['maxfilesize']
59
+ recentpeers = REDIS.lrange(IBDB_RECENT_PEERS, 0, 10).map do |peer|
60
+ digest = Iceberg.ip2digest(peer)
61
+ peerinfo = JSON.parse(REDIS.hget(IBDB_PEERS, digest) || '{}')
62
+ [digest, peerinfo['download']]
63
+ end
64
+ haml :index, :locals => { :recentfiles => recentfiles,
65
+ :uploaded => uploaded, :tripcodelist => tripcodelist,
66
+ :filemax => filemax, :maxfilesize => maxfilesize,
67
+ :recentpeers => recentpeers }
68
+ end
69
+
70
+ get '/api/v1/recentfiles' do
71
+ filemax = SETTING['local']['filemax']
72
+ recentfiles = REDIS.lrange(IBDB_RECENT, 0, filemax)
73
+ rv = { :recentfiles => recentfiles, :filemax => filemax }
74
+ rv.to_json + "\n"
75
+ end
76
+
77
+ get '/api/v1/tripcodelist' do
78
+ tripcodelist = REDIS.smembers(IBDB_TRIPCODE_SET)
79
+ rv = { :tripcodelist => tripcodelist }
80
+ rv.to_json + "\n"
81
+ end
82
+
83
+ post '/upload' do
84
+ f = params[:file]
85
+ redirect '/' if f.nil? # TODO error
86
+ path = f[:tempfile].path
87
+ begin
88
+ filemax = SETTING['local']['filemax']
89
+ maxfilesize = SETTING['local']['maxfilesize']
90
+ rv = Iceberg.upload(path, params[:tripkey], maxfilesize, filemax)
91
+ origname = f[:filename]
92
+ origname = File.basename(origname)
93
+ origname = NKF.nkf("-w", origname) # TODO i18n
94
+ origname = origname.tr(FORBIDDEN_CHARS, "_")
95
+ rv[:name] = origname
96
+ session[:uploaded] = rv
97
+ rescue => x
98
+ p x
99
+ end
100
+ redirect '/'
101
+ end
102
+
103
+ post '/api/v1/upload' do
104
+ begin
105
+ f = params[:file]
106
+ raise if f.nil?
107
+ path = f[:tempfile].path
108
+ filemax = SETTING['local']['filemax']
109
+ maxfilesize = SETTING['local']['maxfilesize']
110
+ rv = Iceberg.upload(path, params[:tripkey], maxfilesize, filemax)
111
+ rescue => x
112
+ rv = { :error => x.to_s }
113
+ end
114
+ content_type CONTENT_TYPES[:js], :charset => 'utf-8'
115
+ rv.to_json + "\n"
116
+ end
117
+
118
+ post '/api/v1/uploadraw' do
119
+ begin
120
+ f = params[:file]
121
+ raise if f.nil?
122
+ path = f[:tempfile].path
123
+ origname = f[:filename] # TODO check SHA-1
124
+ filemax = SETTING['local']['filemax']
125
+ maxfilesize = SETTING['local']['maxfilesize']
126
+ rv = Iceberg.uploadraw(path, maxfilesize, filemax)
127
+ rescue => x
128
+ rv = { :error => x.to_s }
129
+ end
130
+ content_type CONTENT_TYPES[:js], :charset => 'utf-8'
131
+ rv.to_json + "\n"
132
+ end
133
+
134
+ post '/uploadtext' do
135
+ # TODO
136
+ text = params[:text]
137
+ title = params[:title]
138
+ begin
139
+ filemax = SETTING['local']['filemax']
140
+ maxfilesize = SETTING['local']['maxfilesize']
141
+ rv = Iceberg.upload(nil, params[:tripkey], maxfilesize, filemax, text)
142
+ origname = "#{title}.txt"
143
+ origname = origname.tr(FORBIDDEN_CHARS, "_")
144
+ rv[:name] = origname
145
+ session[:uploaded] = rv
146
+ rescue => x
147
+ p x
148
+ end
149
+ redirect '/'
150
+ end
151
+
152
+ get '/show/:name' do
153
+ name = params[:name]
154
+ filename = params[:filename]
155
+ hexdigest = params[:digest]
156
+ b = Storage.new
157
+ o = b.getobject(name)
158
+ ex = o.exists?
159
+ size = ex ? o.content_length : nil
160
+ haml :show, :locals => {:name => name, :filename => filename,
161
+ :hexdigest => hexdigest, :filesize => size,
162
+ :exists => o.exists?}
163
+ end
164
+
165
+ get '/container/:name' do
166
+ name = params[:name]
167
+ hexdigest = params[:digest]
168
+ haml :container, :locals => { :name => name, :hexdigest => hexdigest }
169
+ end
170
+
171
+ ['/download/:name', '/api/v1/download/:name'].each do |path|
172
+ get path do
173
+ name = params[:name]
174
+ filename = params[:filename]
175
+ hexdigest = params[:digest]
176
+ ctype, disp, file, cipher = Iceberg.download(name, filename, hexdigest)
177
+ error 404 unless file.exists?
178
+ if ctype == 'text/plain'
179
+ content_type ctype, :charset => 'utf-8'
180
+ else
181
+ content_type ctype
182
+ end
183
+ if filename
184
+ response.headers['Content-Disposition'] =
185
+ "#{disp}; filename=\"#{filename}\""
186
+ end
187
+ stream do |out|
188
+ begin
189
+ file.read do |data|
190
+ data = cipher.update(data) if cipher
191
+ out << data
192
+ sleep 0.1 # TODO
193
+ end
194
+ out << cipher.final if cipher
195
+ rescue => x
196
+ p x
197
+ end
198
+ Iceberg.recordip(request.ip)
199
+ end
200
+ end
201
+ end
202
+
203
+ get '/tripcode/:tripcode' do
204
+ tripcode = params[:tripcode]
205
+ files = REDIS.smembers(IBDB_TRIPCODE + tripcode)
206
+ fund = REDIS.get(IBDB_TRIPCODE_FUND + tripcode)
207
+ haml :tripcode, :locals => { :tripcode => tripcode, :files => files,
208
+ :fund => fund }
209
+ end
210
+
211
+ end
212
+
213
+ end
data/lib/ibg.rb ADDED
@@ -0,0 +1,185 @@
1
+ # coding: utf-8
2
+ require 'rubygems'
3
+ require 'fileutils'
4
+ require 'redis'
5
+ require 'yaml'
6
+ require 'openssl'
7
+ require "ibg/version"
8
+ require 'ibg/storage'
9
+ require 'ibg/web'
10
+
11
+ module Iceberg
12
+
13
+ HOME_DIR = ENV['HOME']
14
+ SETTING_DIR = File.join(HOME_DIR, '.iceberg')
15
+ SETTING_FILE = File.join(SETTING_DIR, 'settings.yaml')
16
+ unless File.exist?(SETTING_DIR)
17
+ FileUtils.mkdir SETTING_DIR
18
+ end
19
+ unless File.exist?(SETTING_FILE)
20
+ open(SETTING_FILE, 'w') do |fd|
21
+ setting = {
22
+ 'local' => {
23
+ 'download' => File.join(SETTING_DIR, 'download'),
24
+ 'filemax' => 200,
25
+ 'maxfilesize' => 20 * 1024 * 1024, # 20 MiB
26
+ 'demourl' => '/show/3f636ca05f41c4a6dfd5f8cbc7a9dc0125b9a9b7?' +
27
+ 'digest=cafcc2df4c3998ba5ab94b5262ef3369502488f7&' +
28
+ 'filename=jBRA8.webm', # Big Buck Bunny
29
+ 'cdn' => 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1',
30
+ 'salt' => rand.to_s,
31
+ 's3bucket' => false,
32
+ },
33
+ }
34
+ fd.puts(YAML.dump(setting))
35
+ end
36
+ end
37
+
38
+ SETTING = YAML.load(File.read(SETTING_FILE))
39
+
40
+ IBDB_RECENT = 'iceberg:recent'
41
+ IBDB_TRIPCODE = 'iceberg:tripcode:'
42
+ IBDB_TRIPCODE_SET = 'iceberg:tripcode:set'
43
+ IBDB_TRIPCODE_FUND = 'iceberg:tripcode:fund:'
44
+ IBDB_RECENT_PEERS = 'iceberg:recentpeers'
45
+ IBDB_PEERS = 'iceberg:peers'
46
+
47
+ ALGORITHM = 'AES-128-CBC'
48
+ REDIS = if ENV['DB_PORT_6379_TCP_PORT']
49
+ host = ENV['DB_PORT_6379_TCP_ADDR']
50
+ port = ENV['DB_PORT_6379_TCP_PORT'].to_i
51
+ Redis.new(:host => host, :port => port)
52
+ else
53
+ Redis.new
54
+ end
55
+
56
+ def self.uploadimpl2(filemax, encdigest)
57
+ n = REDIS.lpush(IBDB_RECENT, encdigest)
58
+ tripcodelist = REDIS.smembers(IBDB_TRIPCODE_SET) || []
59
+ while n.size > filemax
60
+ n = REDIS.llen(IBDB_RECENT)
61
+ dropdigest = REDIS.rpop(IBDB_RECENT)
62
+ b = Iceberg::Storage.new
63
+ dropfile = b.getobject(dropdigest)
64
+ dropsize = dropfile.content_length / (1024 * 1024) # MiB
65
+ dropsize = 1 if dropsize <= 0 # TODO handle under 1 MiB
66
+ found = false
67
+ tripcodelist.each do |tripcode|
68
+ next unless REDIS.sismember(IBDB_TRIPCODE + tripcode, dropdigest)
69
+ v = REDIS.get(IBDB_TRIPCODE_FUND + tripcode)
70
+ if v.to_i > 0
71
+ REDIS.decrby(IBDB_TRIPCODE_FUND + tripcode, dropsize)
72
+ found = true
73
+ break
74
+ end
75
+ end
76
+ unless found
77
+ # TODO do not remove small files (for now)
78
+ dropfile.delete if dropfile.content_length > 64 * 1024 # 64 KiB
79
+ break
80
+ end
81
+ REDIS.lpush(IBDB_RECENT, dropdigest)
82
+ end
83
+ end
84
+
85
+ def self.upload(path, tripkey, filesize, filemax, text = nil)
86
+ tripkey = nil if tripkey.empty?
87
+ if path
88
+ raise 'size over' if File.new(path).size > filesize
89
+ alldata = File.open(path, 'rb'){|fd| fd.read}
90
+ else
91
+ raise 'size over' if text.size > filesize # TODO convert to binary size
92
+ alldata = text
93
+ end
94
+ digest = Digest::SHA1.digest(alldata)
95
+ hexdigest = digest.unpack('H*')[0]
96
+ cipher = OpenSSL::Cipher::Cipher.new(ALGORITHM).encrypt
97
+ cipher.key = digest[0, 16]
98
+ cipher.iv = digest[4, 16]
99
+ encdata = cipher.update(alldata) + cipher.final
100
+ encdigest = Digest::SHA1.hexdigest(encdata)
101
+
102
+ b = Iceberg::Storage.new
103
+ dest = b.getobject(encdigest)
104
+ unless dest.exists?
105
+ dest.write(encdata)
106
+ dest.close rescue nil # TODO
107
+ uploadimpl2(filemax, encdigest)
108
+ end
109
+ if tripkey
110
+ tripcode = Base64.encode64(Digest::SHA1.digest(tripkey))[0, 12]
111
+ tripcode = tripcode.tr('/', '.')
112
+ REDIS.sadd(IBDB_TRIPCODE_SET, tripcode)
113
+ REDIS.sadd(IBDB_TRIPCODE + tripcode, encdigest)
114
+ # TODO test (initial bonus)
115
+ v = REDIS.get(IBDB_TRIPCODE_FUND + tripcode)
116
+ if v
117
+ REDIS.incrby(IBDB_TRIPCODE_FUND + tripcode, 1)
118
+ else
119
+ REDIS.set(IBDB_TRIPCODE_FUND + tripcode, 2)
120
+ end
121
+ end
122
+ {
123
+ :digest => hexdigest,
124
+ :encdigest => encdigest,
125
+ :tripcode => tripcode,
126
+ }
127
+ end
128
+
129
+ def self.uploadraw(path, filesize, filemax)
130
+ raise 'size over' if File.new(path).size > filesize
131
+ encdata = File.open(path, 'rb'){|fd| fd.read} # TODO large file
132
+ encdigest = Digest::SHA1.hexdigest(encdata)
133
+ # TODO check filename
134
+ b = Iceberg::Storage.new
135
+ dest = b.getobject(encdigest)
136
+ unless dest.exists?
137
+ dest.write(encdata)
138
+ dest.close rescue nil # TODO
139
+ uploadimpl2(filemax, encdigest)
140
+ end
141
+ {
142
+ }
143
+ end
144
+
145
+ def self.download(name, filename, hexdigest)
146
+ ctype, disp = case filename
147
+ when /\.jpg$/ ; ['image/jpeg', 'inline']
148
+ when /\.png$/ ; ['image/png', 'inline']
149
+ when /\.gif$/ ; ['image/gif', 'inline']
150
+ when /\.mp3$/ ; ['audio/mpeg', 'inline']
151
+ when /\.ogg$/ ; ['audio/ogg', 'inline']
152
+ when /\.flac$/ ; ['audio/flac', 'inline']
153
+ when /\.webm$/ ; ['video/webm', 'inline']
154
+ when /\.txt$/ ; ['text/plain', 'inline']
155
+ else ['application/octet-stream', 'attachment']
156
+ end
157
+ if hexdigest
158
+ digest = [hexdigest].pack('H*')
159
+ cipher = OpenSSL::Cipher::Cipher.new(ALGORITHM).decrypt
160
+ cipher.key = digest[0, 16]
161
+ cipher.iv = digest[4, 16]
162
+ end
163
+ b = Iceberg::Storage.new
164
+ file = b.getobject(name)
165
+ [ctype, disp, file, cipher]
166
+ end
167
+
168
+ def self.ip2digest(ip)
169
+ salt = SETTING['local']['salt'] || ''
170
+ Digest::SHA1.hexdigest(salt + ip)
171
+ end
172
+
173
+ def self.recordip(ip)
174
+ info = JSON.parse(REDIS.hget(IBDB_PEERS, ip2digest(ip)) || '{}')
175
+ info['download'] ||= 0
176
+ info['download'] += 1
177
+ REDIS.hset(IBDB_PEERS, ip2digest(ip), info.to_json)
178
+ REDIS.lrem(IBDB_RECENT_PEERS, 1, ip)
179
+ REDIS.lpush(IBDB_RECENT_PEERS, ip)
180
+ if REDIS.llen(IBDB_RECENT_PEERS) > 10 # TODO
181
+ REDIS.rpop(IBDB_RECENT_PEERS)
182
+ end
183
+ end
184
+
185
+ end
@@ -0,0 +1 @@
1
+ (function(c){var b=function(d,e){this.options=e;this.$elementFilestyle=[];this.$element=c(d)};b.prototype={clear:function(){this.$element.val("");this.$elementFilestyle.find(":text").val("");this.$elementFilestyle.find(".badge").remove()},destroy:function(){this.$element.removeAttr("style").removeData("filestyle").val("");this.$elementFilestyle.remove()},disabled:function(d){if(d===true){if(!this.options.disabled){this.$element.attr("disabled","true");this.$elementFilestyle.find("label").attr("disabled","true");this.options.disabled=true}}else{if(d===false){if(this.options.disabled){this.$element.removeAttr("disabled");this.$elementFilestyle.find("label").removeAttr("disabled");this.options.disabled=false}}else{return this.options.disabled}}},buttonBefore:function(d){if(d===true){if(!this.options.buttonBefore){this.options.buttonBefore=true;if(this.options.input){this.$elementFilestyle.remove();this.constructor();this.pushNameFiles()}}}else{if(d===false){if(this.options.buttonBefore){this.options.buttonBefore=false;if(this.options.input){this.$elementFilestyle.remove();this.constructor();this.pushNameFiles()}}}else{return this.options.buttonBefore}}},icon:function(d){if(d===true){if(!this.options.icon){this.options.icon=true;this.$elementFilestyle.find("label").prepend(this.htmlIcon())}}else{if(d===false){if(this.options.icon){this.options.icon=false;this.$elementFilestyle.find(".glyphicon").remove()}}else{return this.options.icon}}},input:function(e){if(e===true){if(!this.options.input){this.options.input=true;if(this.options.buttonBefore){this.$elementFilestyle.append(this.htmlInput())}else{this.$elementFilestyle.prepend(this.htmlInput())}this.$elementFilestyle.find(".badge").remove();this.pushNameFiles();this.$elementFilestyle.find(".group-span-filestyle").addClass("input-group-btn")}}else{if(e===false){if(this.options.input){this.options.input=false;this.$elementFilestyle.find(":text").remove();var d=this.pushNameFiles();if(d.length>0&&this.options.badge){this.$elementFilestyle.find("label").append(' <span class="badge">'+d.length+"</span>")}this.$elementFilestyle.find(".group-span-filestyle").removeClass("input-group-btn")}}else{return this.options.input}}},size:function(d){if(d!==undefined){var f=this.$elementFilestyle.find("label"),e=this.$elementFilestyle.find("input");f.removeClass("btn-lg btn-sm");e.removeClass("input-lg input-sm");if(d!="nr"){f.addClass("btn-"+d);e.addClass("input-"+d)}}else{return this.options.size}},buttonText:function(d){if(d!==undefined){this.options.buttonText=d;this.$elementFilestyle.find("label span").html(this.options.buttonText)}else{return this.options.buttonText}},buttonName:function(d){if(d!==undefined){this.options.buttonName=d;this.$elementFilestyle.find("label").attr({"class":"btn "+this.options.buttonName})}else{return this.options.buttonName}},iconName:function(d){if(d!==undefined){this.$elementFilestyle.find(".glyphicon").attr({"class":".glyphicon "+this.options.iconName})}else{return this.options.iconName}},htmlIcon:function(){if(this.options.icon){return'<span class="glyphicon '+this.options.iconName+'"></span> '}else{return""}},htmlInput:function(){if(this.options.input){return'<input type="text" class="form-control '+(this.options.size=="nr"?"":"input-"+this.options.size)+'" disabled> '}else{return""}},pushNameFiles:function(){var d="",f=[];if(this.$element[0].files===undefined){f[0]={name:this.$element[0]&&this.$element[0].value}}else{f=this.$element[0].files}for(var e=0;e<f.length;e++){d+=f[e].name.split("\\").pop()+", "}if(d!==""){this.$elementFilestyle.find(":text").val(d.replace(/\, $/g,""))}else{this.$elementFilestyle.find(":text").val("")}return f},constructor:function(){var h=this,f="",g=h.$element.attr("id"),d=[],i="",e;if(g===""||!g){g="filestyle-"+c(".bootstrap-filestyle").length;h.$element.attr({id:g})}i='<span class="group-span-filestyle '+(h.options.input?"input-group-btn":"")+'"><label for="'+g+'" class="btn '+h.options.buttonName+" "+(h.options.size=="nr"?"":"btn-"+h.options.size)+'" '+(h.options.disabled?'disabled="true"':"")+">"+h.htmlIcon()+h.options.buttonText+"</label></span>";f=h.options.buttonBefore?i+h.htmlInput():h.htmlInput()+i;h.$elementFilestyle=c('<div class="bootstrap-filestyle input-group">'+f+"</div>");h.$elementFilestyle.find(".group-span-filestyle").attr("tabindex","0").keypress(function(j){if(j.keyCode===13||j.charCode===32){h.$elementFilestyle.find("label").click();return false}});h.$element.css({position:"absolute",clip:"rect(0px 0px 0px 0px)"}).attr("tabindex","-1").after(h.$elementFilestyle);if(h.options.disabled){h.$element.attr("disabled","true")}h.$element.change(function(){var j=h.pushNameFiles();if(h.options.input==false&&h.options.badge){if(h.$elementFilestyle.find(".badge").length==0){h.$elementFilestyle.find("label").append(' <span class="badge">'+j.length+"</span>")}else{if(j.length==0){h.$elementFilestyle.find(".badge").remove()}else{h.$elementFilestyle.find(".badge").html(j.length)}}}else{h.$elementFilestyle.find(".badge").remove()}});if(window.navigator.userAgent.search(/firefox/i)>-1){h.$elementFilestyle.find("label").click(function(){h.$element.click();return false})}}};var a=c.fn.filestyle;c.fn.filestyle=function(e,d){var f="",g=this.each(function(){if(c(this).attr("type")==="file"){var j=c(this),h=j.data("filestyle"),i=c.extend({},c.fn.filestyle.defaults,e,typeof e==="object"&&e);if(!h){j.data("filestyle",(h=new b(this,i)));h.constructor()}if(typeof e==="string"){f=h[e](d)}}});if(typeof f!==undefined){return f}else{return g}};c.fn.filestyle.defaults={buttonText:"Choose file",iconName:"glyphicon-folder-open",buttonName:"btn-default",size:"nr",input:true,badge:true,icon:true,buttonBefore:false,disabled:false};c.fn.filestyle.noConflict=function(){c.fn.filestyle=a;return this};c(function(){c(".filestyle").each(function(){var e=c(this),d={input:e.attr("data-input")==="false"?false:true,icon:e.attr("data-icon")==="false"?false:true,buttonBefore:e.attr("data-buttonBefore")==="true"?true:false,disabled:e.attr("data-disabled")==="true"?true:false,size:e.attr("data-size"),buttonText:e.attr("data-buttonText"),buttonName:e.attr("data-buttonName"),iconName:e.attr("data-iconName"),badge:e.attr("data-badge")==="false"?false:true};e.filestyle(d)})})})(window.jQuery);
@@ -0,0 +1,134 @@
1
+ /* GLOBAL STYLES
2
+ -------------------------------------------------- */
3
+ /* Padding below the footer and lighter body text */
4
+
5
+ body {
6
+ padding-bottom: 40px;
7
+ color: #5a5a5a;
8
+ }
9
+
10
+
11
+ /* CUSTOMIZE THE NAVBAR
12
+ -------------------------------------------------- */
13
+
14
+ /* Special class on .container surrounding .navbar, used for positioning it into place. */
15
+ .navbar-wrapper {
16
+ position: absolute;
17
+ top: 0;
18
+ right: 0;
19
+ left: 0;
20
+ z-index: 20;
21
+ }
22
+
23
+ /* Flip around the padding for proper display in narrow viewports */
24
+ .navbar-wrapper > .container {
25
+ padding-right: 0;
26
+ padding-left: 0;
27
+ }
28
+ .navbar-wrapper .navbar {
29
+ padding-right: 15px;
30
+ padding-left: 15px;
31
+ }
32
+ .navbar-wrapper .navbar .container {
33
+ width: auto;
34
+ }
35
+
36
+
37
+ /* CUSTOMIZE THE CAROUSEL
38
+ -------------------------------------------------- */
39
+
40
+ /* Carousel base class */
41
+ .carousel {
42
+ height: 400px;
43
+ margin-bottom: 60px;
44
+ }
45
+ /* Since positioning the image, we need to help out the caption */
46
+ .carousel-caption {
47
+ z-index: 10;
48
+ }
49
+
50
+ /* Declare heights because of positioning of img element */
51
+ .carousel .item {
52
+ height: 400px;
53
+ background-color: #777;
54
+ }
55
+ .carousel-inner > .item > img {
56
+ position: absolute;
57
+ top: 0;
58
+ left: 0;
59
+ min-width: 100%;
60
+ height: 400px;
61
+ }
62
+
63
+
64
+ /* MARKETING CONTENT
65
+ -------------------------------------------------- */
66
+
67
+ /* Center align the text within the three columns below the carousel */
68
+ .marketing .col-lg-4 {
69
+ margin-bottom: 20px;
70
+ text-align: center;
71
+ }
72
+ .marketing h2 {
73
+ font-weight: normal;
74
+ }
75
+ .marketing .col-lg-4 p {
76
+ margin-right: 10px;
77
+ margin-left: 10px;
78
+ }
79
+
80
+
81
+ /* Featurettes
82
+ ------------------------- */
83
+
84
+ .featurette-divider {
85
+ margin: 80px 0; /* Space out the Bootstrap <hr> more */
86
+ }
87
+
88
+ /* Thin out the marketing headings */
89
+ .featurette-heading {
90
+ font-weight: 300;
91
+ line-height: 1;
92
+ letter-spacing: -1px;
93
+ }
94
+
95
+
96
+ /* RESPONSIVE CSS
97
+ -------------------------------------------------- */
98
+
99
+ @media (min-width: 768px) {
100
+ /* Navbar positioning foo */
101
+ .navbar-wrapper {
102
+ margin-top: 20px;
103
+ }
104
+ .navbar-wrapper .container {
105
+ padding-right: 15px;
106
+ padding-left: 15px;
107
+ }
108
+ .navbar-wrapper .navbar {
109
+ padding-right: 0;
110
+ padding-left: 0;
111
+ }
112
+
113
+ /* The navbar becomes detached from the top, so we round the corners */
114
+ .navbar-wrapper .navbar {
115
+ border-radius: 4px;
116
+ }
117
+
118
+ /* Bump up size of carousel content */
119
+ .carousel-caption p {
120
+ margin-bottom: 20px;
121
+ font-size: 21px;
122
+ line-height: 1.4;
123
+ }
124
+
125
+ .featurette-heading {
126
+ font-size: 50px;
127
+ }
128
+ }
129
+
130
+ @media (min-width: 992px) {
131
+ .featurette-heading {
132
+ margin-top: 120px;
133
+ }
134
+ }
Binary file
data/public/humans.txt ADDED
@@ -0,0 +1,2 @@
1
+ Developer: https://twitter.com/ohac
2
+ GitHub: https://github.com/ohac/iceberg
data/public/index.js ADDED
@@ -0,0 +1,88 @@
1
+ $(function(){
2
+ var encdigest = $('#encdigest').val();
3
+ if (encdigest) {
4
+ var name = $('#name').val();
5
+ var digest = $('#digest').val();
6
+ var tripcode = $('#tripcode').val();
7
+ localStorage.setItem(encdigest + ':name', name);
8
+ localStorage.setItem(encdigest + ':digest', digest);
9
+ if (tripcode) {
10
+ var list = localStorage.getItem('tripcodelist');
11
+ if (list) {
12
+ if (list.indexOf(tripcode) < 0) {
13
+ localStorage.setItem('tripcodelist', list + ',' + tripcode);
14
+ }
15
+ }
16
+ else {
17
+ localStorage.setItem('tripcodelist', tripcode);
18
+ }
19
+ }
20
+ }
21
+ $('.files').each(function(i, x) {
22
+ var y = $(x);
23
+ var id = y.attr('id');
24
+ var digest = localStorage.getItem(id + ':digest');
25
+ if (digest) {
26
+ var name = localStorage.getItem(id + ':name');
27
+ var href = y.attr('href');
28
+ href = href + '?digest=' + digest + '&filename=' + name;
29
+ y.attr('href', href);
30
+ y.html(name);
31
+ }
32
+ else {
33
+ y.hide();
34
+ }
35
+ });
36
+ $('.tripcodelist').each(function(i, x) {
37
+ var y = $(x);
38
+ var id = y.attr('id');
39
+ var tripcodelist = localStorage.getItem('tripcodelist');
40
+ var star = '<span class="glyphicon glyphicon-star" aria-hidden="true">' +
41
+ '</span>';
42
+ var html = y.html();
43
+ var tagname = localStorage.getItem(id + ':title');
44
+ tagname = tagname ? tagname : ('Untitled Tag ' + id);
45
+ html = tagname;
46
+ if (tripcodelist && tripcodelist.indexOf(id) >= 0) {
47
+ html = star + html;
48
+ }
49
+ y.html(html);
50
+ });
51
+ $('#showkeys').click(function () {
52
+ var metadata = ''
53
+ var sepa = ''
54
+ for (var i = 0; i < localStorage.length; i++) {
55
+ var k = localStorage.key(i);
56
+ metadata = metadata + sepa + k + '=' + localStorage.getItem(k);
57
+ sepa = '\n';
58
+ }
59
+ $('#metadata').val(metadata);
60
+ });
61
+ $('#apply').click(function () {
62
+ var metadata = $('#metadata').val();
63
+ $(metadata.split('\n')).each(function (i, x) {
64
+ var kv = x.split('=');
65
+ var k = kv[0];
66
+ var v = kv[1];
67
+ if (v) {
68
+ if (v.length == 0) {
69
+ localStorage.removeItem(k);
70
+ }
71
+ else {
72
+ localStorage.setItem(k, v);
73
+ }
74
+ }
75
+ });
76
+ location.href = '/';
77
+ });
78
+ $('#deleteall').click(function () {
79
+ if (confirm('Are you sure?')) {
80
+ localStorage.clear();
81
+ }
82
+ });
83
+ $(":file").filestyle({input: false, icon: true, size: 'lg'});
84
+ $('#uploadtabs a').click(function (e) {
85
+ e.preventDefault()
86
+ $(this).tab('show')
87
+ });
88
+ });