flunkey 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c8b52f01502921d757e53bfdaf29a62ecb178c4d
4
+ data.tar.gz: b9bae54b48876639eaf00bd3a498bd62f580aba8
5
+ SHA512:
6
+ metadata.gz: 094bccf13af82fee6a894e2b2c917485b5fe6e5338b7d44ad5dcdfdc379e137141e334a16c3e44a5210b9add5231633bf0487ae72e0fee70f3824571ee868396
7
+ data.tar.gz: b39beb4c26ff0f65670811f8af02990a98dc3ac4b9be5518d12be03a40a64aa8b586c74136259f18300f3a1eba591ba749c786725bd9327442fe0a001a4f41ce
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .ruby-*
4
+ .bundle
5
+ .config
6
+ coverage
7
+ InstalledFiles
8
+ lib/bundler/man
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in flunkey.gemspec
4
+ gemspec
5
+
6
+ gem 'rack-test', '>= 0.5.6'
7
+ gem 'rspec'
@@ -0,0 +1,65 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ flunkey (0.0.1)
5
+ activesupport
6
+ sinatra
7
+ sinatra-contrib
8
+ sinatra-partial
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activesupport (4.0.1)
14
+ i18n (~> 0.6, >= 0.6.4)
15
+ minitest (~> 4.2)
16
+ multi_json (~> 1.3)
17
+ thread_safe (~> 0.1)
18
+ tzinfo (~> 0.3.37)
19
+ atomic (1.1.14)
20
+ backports (3.3.5)
21
+ diff-lcs (1.2.4)
22
+ i18n (0.6.5)
23
+ minitest (4.7.5)
24
+ multi_json (1.8.2)
25
+ rack (1.5.2)
26
+ rack-protection (1.5.1)
27
+ rack
28
+ rack-test (0.6.2)
29
+ rack (>= 1.0)
30
+ rake (10.1.0)
31
+ rspec (2.14.1)
32
+ rspec-core (~> 2.14.0)
33
+ rspec-expectations (~> 2.14.0)
34
+ rspec-mocks (~> 2.14.0)
35
+ rspec-core (2.14.7)
36
+ rspec-expectations (2.14.3)
37
+ diff-lcs (>= 1.1.3, < 2.0)
38
+ rspec-mocks (2.14.4)
39
+ sinatra (1.4.4)
40
+ rack (~> 1.4)
41
+ rack-protection (~> 1.4)
42
+ tilt (~> 1.3, >= 1.3.4)
43
+ sinatra-contrib (1.4.1)
44
+ backports (>= 2.0)
45
+ multi_json
46
+ rack-protection
47
+ rack-test
48
+ sinatra (~> 1.4.0)
49
+ tilt (~> 1.3)
50
+ sinatra-partial (0.4.0)
51
+ sinatra
52
+ thread_safe (0.1.3)
53
+ atomic
54
+ tilt (1.4.1)
55
+ tzinfo (0.3.38)
56
+
57
+ PLATFORMS
58
+ ruby
59
+
60
+ DEPENDENCIES
61
+ bundler (~> 1.3)
62
+ flunkey!
63
+ rack-test (>= 0.5.6)
64
+ rake
65
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Christian Aust
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # Flunkey
2
+
3
+ Flunkey is a minimal download portal. It's designed as a add-on to [OS X Server](http://www.apple.com/osx/server/), using technologies like [QuickLook](http://en.wikipedia.org/wiki/Quick_Look) and [Spotlight](http://en.wikipedia.org/wiki/Spotlight_\(software\)) available on OS X.
4
+
5
+ Flunkey lets users download files and directories over the web. It's easier to use than plain FTP and offers additional abilities like fast file preview and on-demand zipping of directories. Flunkey integrates with the standard web services on OS X Server: It can be configured on any web host, with or without SSL. Authentication is handled by OS X Server, allowing users and/or groups to use the download facility.
6
+
7
+ ## Requirements
8
+
9
+ * OS X
10
+ * Ruby 1.9 or higher
11
+ * Activated web services
12
+
13
+ OS X Server is recommended but not actually required. Since Flunkey doesn't do user authentication, you'd need something else than OS X Server to maintain a list of user credentials.
14
+
15
+ ## Installation
16
+
17
+ Install it on OS X as:
18
+
19
+ $ gem install flunkey
20
+
21
+ OS X 10.9 comes standard with Ruby 2.0. Earlier releases of OS X include Ruby 1.8, you need to install Ruby 1.9 or higher using [RVM](http://rvm.io) or other means.
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create new Pull Request
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+
6
+ require 'rspec/core/rake_task'
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task :default => :spec
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.test_files = FileList['test/*_test.rb']
14
+ t.ruby_opts = ['-rubygems'] if defined? Gem
15
+ t.ruby_opts << '-I.'
16
+ t.warning = true
17
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'rubygems'
6
+
7
+ if File.file?(lib + '/flunkey/version.rb')
8
+ $LOAD_PATH << lib
9
+ else
10
+ gem 'flunkey'
11
+ end
12
+
13
+ require 'flunkey'
14
+ require 'flunkey/application'
15
+
16
+ Flunkey::Application.run
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flunkey/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "flunkey"
8
+ spec.version = Flunkey::VERSION
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.authors = ["Christian Aust"]
11
+ spec.email = ["git@kontakt.software-consultant.net"]
12
+ spec.description = %q{Flunkey is a minimal download portal.}
13
+ spec.summary = %q{Flunkey is a minimal download portal.}
14
+ spec.homepage = ""
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+
25
+ spec.add_dependency "activesupport"
26
+ spec.add_dependency "sinatra"
27
+ spec.add_dependency "sinatra-contrib"
28
+ spec.add_dependency "sinatra-partial"
29
+ end
@@ -0,0 +1,13 @@
1
+ module Flunkey; end
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+
5
+ require 'flunkey/version'
6
+ require 'flunkey/helpers'
7
+ require 'flunkey/functions'
8
+ require 'flunkey/server'
9
+ require 'flunkey/metadata'
10
+ require 'flunkey/pathname_extensions'
11
+
12
+ require 'pathname'
13
+ Pathname.send :include, Flunkey::PathnameExtensions
@@ -0,0 +1,53 @@
1
+ require 'optparse'
2
+ require 'rack'
3
+ require 'flunkey'
4
+
5
+ module Flunkey
6
+ class Application
7
+ attr_accessor :options
8
+
9
+ def run(args = ARGV)
10
+ @options = parse(args)
11
+ run_server
12
+ end
13
+
14
+ def default_options
15
+ root = File.expand_path('../../', File.dirname(__FILE__))
16
+ {
17
+ root: root,
18
+ port: 8080,
19
+ address: '127.0.0.1',
20
+ paths: [root]
21
+ }
22
+ end
23
+
24
+ def parse(args)
25
+ optparse = OptionParser.new do|opts|
26
+ opts.on_tail("-h", "--help", "Show this message") do
27
+ puts opts
28
+ exit
29
+ end
30
+
31
+ opts.on_tail("--version", "Show version") do
32
+ puts Flunkey::VERSION
33
+ exit
34
+ end
35
+ end
36
+ optparse.parse(args)
37
+ options.merge default_options
38
+ end
39
+
40
+ def self.run(args = ARGV)
41
+ new.run(args)
42
+ end
43
+
44
+ def initialize
45
+ self.options = {}
46
+ end
47
+
48
+ def run_server
49
+ Flunkey::Server.run! self.options
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,22 @@
1
+ module Flunkey
2
+ module Functions
3
+ def recognize(path)
4
+ return if path.nil?
5
+ path = Pathname.new(path) unless path === Pathname
6
+ if path.relative?
7
+ first_dir = path.to_s.split('/').first
8
+ root = settings.paths.find{|p| p.basename.to_s == first_dir }
9
+ return [root, (root.root? ? Pathname.new('/') : root.parent) + path]
10
+ end
11
+ root = settings.paths.find{|p| p.is_parent_of?(path) }
12
+ [root, path]
13
+ end
14
+
15
+ def search(query)
16
+ cmd = %w{/usr/bin/mdfind}
17
+ settings.paths.each { |p| cmd << "-onlyin #{p}" }
18
+ cmd << "\"#{query}\""
19
+ `#{cmd.join(' ')}`.lines.map{|l| recognize(l.strip) }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,64 @@
1
+ module Flunkey
2
+ module Helpers
3
+
4
+ # Byte per second
5
+ DOWNLOAD_SPEEDS = {
6
+ 'Modem'=>7000,
7
+ 'EDGE'=>24500,
8
+ 'DSL Light'=>96000,
9
+ 'DSL 3000'=>375000,
10
+ 'DSL 16000'=>2000000
11
+ }
12
+
13
+ def config
14
+ settings
15
+ end
16
+
17
+ def title
18
+ return @title if @title
19
+ if @path && @root
20
+ return (@root.basename + @path.relative_path_from(@root)).to_s
21
+ end
22
+ "Flunkey #{Flunkey::VERSION}"
23
+ end
24
+
25
+ def icon_for(path)
26
+ icon = case
27
+ when path.directory? then "fa-folder"
28
+ when path.bundle? then "fa-file-o"
29
+ else "fa-file-o"
30
+ end
31
+ "<i class='fa #{icon}'></i>"
32
+ end
33
+
34
+ def to_url(root, path)
35
+ "/#{root.basename + path.relative_path_from(root)}"
36
+ end
37
+
38
+ def breadcrumbs(root, path)
39
+ b = [root.dup]
40
+ return b if root==path
41
+ path.relative_path_from(root).descend{|p| b << root + p }
42
+ b
43
+ end
44
+
45
+ def download_estimates(size)
46
+ result = []
47
+ DOWNLOAD_SPEEDS.each do |name, speed|
48
+ result << [name, (size/speed).round(1)]
49
+ end
50
+ result.sort{|a,b| b[1]<=>a[1]}
51
+ end
52
+
53
+ def localize(date)
54
+ date.strftime('%d.%m.%Y %H:%M')
55
+ end
56
+
57
+ alias_method :l, :localize
58
+
59
+ def debug(obj)
60
+ "<pre>#{Rack::Utils.escape_html obj.inspect}</pre>"
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,48 @@
1
+ module Flunkey
2
+ class Metadata < Hash
3
+ SINGLE = /(\S+)\s+=\s{1}(.*)/
4
+ ARRAY = /(\S+)/
5
+
6
+ def self.parse(string)
7
+ meta = Metadata.new
8
+
9
+ lines = string.lines
10
+ idx = 0
11
+ arr_key = arr_value = nil
12
+
13
+ while idx < lines.length
14
+
15
+ if arr_key
16
+ ARRAY.match(lines[idx]) do |m|
17
+ value = m[1]
18
+ if value == ')'
19
+ # array is complete
20
+ meta[arr_key] = arr_value
21
+ arr_key = arr_value = nil
22
+ else
23
+ value.gsub!(/,$/, '')
24
+ arr_value << (value=='(null)' ? nil : value.gsub('"',''))
25
+ end
26
+ end
27
+ else
28
+ SINGLE.match(lines[idx]) do |m|
29
+ key, value = m[1], m[2]
30
+ if value=='('
31
+ # we'll start an array
32
+ arr_key = key
33
+ arr_value = []
34
+ else
35
+ meta[key] = (value=='(null)' ? nil : value.gsub('"',''))
36
+ end
37
+ end
38
+ end
39
+
40
+ idx += 1
41
+ end
42
+
43
+ meta.each { |k, v| meta[k] = DateTime.parse(v) if k =~ /Date/ }
44
+
45
+ meta
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,61 @@
1
+ module Flunkey
2
+ module PathnameExtensions
3
+
4
+ BUNDLE_TEST = /com\.apple\.bundle/
5
+ EXCLUDE_PATTERNS = [
6
+ /^\..*/
7
+ ]
8
+
9
+ def exclude_patterns
10
+ @exclude_patterns || EXCLUDE_PATTERNS
11
+ end
12
+
13
+ def browsable?
14
+ return false unless exist? && readable?
15
+ return false if bundle?
16
+ return true if directory?
17
+ false
18
+ end
19
+
20
+ def downloadable?
21
+ return false unless exist? && readable?
22
+ return false if directory?
23
+ true
24
+ end
25
+
26
+ def compressable?
27
+ return false unless exist? && readable?
28
+ true
29
+ end
30
+
31
+ def bundle?
32
+ return false unless directory?
33
+ return true if `/usr/bin/mdls -name kMDItemContentTypeTree #{to_s}` =~ BUNDLE_TEST
34
+ false
35
+ end
36
+
37
+ def excluded?
38
+ return true if exclude_patterns.any?{|pat| pat.match self.basename.to_s}
39
+ end
40
+
41
+ def metadata(*keys)
42
+ @metadata ||= Flunkey::Metadata.parse `/usr/bin/mdls #{to_s}`
43
+ end
44
+
45
+ def is_parent_of?(path)
46
+ return false if path.nil?
47
+ return false unless self.absolute? && path.absolute?
48
+ return false if self == path
49
+ p = path.to_s.split('/')
50
+ self.to_s.split('/').each_with_index { |elem, idx|
51
+ return false unless elem == p[idx]
52
+ }
53
+ true
54
+ end
55
+
56
+ def filtered_children
57
+ children.delete_if(&:excluded?)
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,65 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/partial'
3
+ require 'sinatra/reloader'
4
+ require 'active_support/number_helper'
5
+
6
+ module Flunkey
7
+ class Server < Sinatra::Base
8
+
9
+ include Functions
10
+
11
+ def initialize(app = nil)
12
+ super(app)
13
+ settings.paths = settings.paths.map{|p| Pathname.new(p) }.find_all{|p| p.browsable?}
14
+ end
15
+
16
+ configure do
17
+ register Sinatra::Partial
18
+ set :partial_template_engine, :erb
19
+ enable :partial_underscores
20
+ end
21
+
22
+ configure :development do
23
+ register Sinatra::Reloader
24
+ end
25
+
26
+ helpers Flunkey::Helpers, ActiveSupport::NumberHelper
27
+
28
+ get '/_search' do
29
+ @title = "Search results"
30
+ @query = params[:query]
31
+ @results = search(@query)
32
+ erb :search
33
+ end
34
+
35
+ get '/*/info' do
36
+ @root, @path = recognize params[:splat].first
37
+ halt(404, erb(:not_found)) unless @root
38
+ erb :info
39
+ end
40
+
41
+ get '/*/ql' do
42
+ @root, @path = recognize params[:splat].first
43
+ halt(404, erb(:not_found)) unless @root
44
+ erb :info
45
+ end
46
+
47
+ get '/*' do
48
+ path_info = params[:splat].first
49
+ pass if path_info.blank?
50
+ @root, @path = recognize path_info
51
+ halt(404, erb(:not_found)) unless @root
52
+ if @path.browsable?
53
+ return erb :path
54
+ end
55
+
56
+ send_file @path, disposition: :attachment
57
+ end
58
+
59
+ get '/' do
60
+ @paths = settings.paths
61
+ erb :index
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module Flunkey
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,44 @@
1
+ .navbar {
2
+ border: none;
3
+ border-bottom: 1px solid #285e8e;
4
+ }
5
+ .navbar-blue {
6
+ background: rgb(126,177,214); /* Old browsers */
7
+ background: -moz-linear-gradient(top, rgba(126,177,214,1) 0%, rgba(66,139,202,1) 50%, rgba(50,106,153,1) 100%); /* FF3.6+ */
8
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(126,177,214,1)), color-stop(50%,rgba(66,139,202,1)), color-stop(100%,rgba(50,106,153,1))); /* Chrome,Safari4+ */
9
+ background: -webkit-linear-gradient(top, rgba(126,177,214,1) 0%,rgba(66,139,202,1) 50%,rgba(50,106,153,1) 100%); /* Chrome10+,Safari5.1+ */
10
+ background: -o-linear-gradient(top, rgba(126,177,214,1) 0%,rgba(66,139,202,1) 50%,rgba(50,106,153,1) 100%); /* Opera 11.10+ */
11
+ background: -ms-linear-gradient(top, rgba(126,177,214,1) 0%,rgba(66,139,202,1) 50%,rgba(50,106,153,1) 100%); /* IE10+ */
12
+ background: linear-gradient(to bottom, rgba(126,177,214,1) 0%,rgba(66,139,202,1) 50%,rgba(50,106,153,1) 100%); /* W3C */
13
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#7eb1d6', endColorstr='#326a99',GradientType=0 ); /* IE6-9 */
14
+ }
15
+
16
+ .navbar-blue .navbar-header a {
17
+ color: #eee;
18
+ font-weight: bold;
19
+ }
20
+
21
+ .filename a i {
22
+ color: black;
23
+ margin-right: 0.8em;
24
+ }
25
+
26
+ .filename a i:hover {
27
+ text-decoration: none;
28
+ }
29
+
30
+ .btn-default i {
31
+ color: #428bca;
32
+ }
33
+
34
+ .btn-default[disabled] i {
35
+ color: #ccc;
36
+ }
37
+
38
+ .dl-horizontal dt {
39
+ text-align: left;
40
+ }
41
+
42
+ footer {
43
+ color: #ccc;
44
+ }
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flunkey::Functions do
4
+
5
+ class Dummy
6
+ include Flunkey::Functions
7
+ attr_accessor :settings
8
+
9
+ def initialize
10
+ @settings = OpenStruct.new
11
+ end
12
+ end
13
+
14
+ subject { Dummy.new.tap{|d| d.settings.paths = %w{/tmp}.map{|s| Pathname.new s} } }
15
+
16
+ it "recognizes paths" do
17
+ subject.recognize(nil).should be_nil
18
+ root = subject.settings.paths.first
19
+ a = Pathname.new '/tmp/a'
20
+ subject.recognize(a).should == [root, a]
21
+
22
+ a = Pathname.new 'tmp/a'
23
+ subject.recognize(a).should == [root, Pathname.new('/tmp/a')]
24
+
25
+ a = Pathname.new 'tmp'
26
+ subject.recognize(a).should == [root, Pathname.new('/tmp')]
27
+
28
+ a = Pathname.new '/foo/bar'
29
+ subject.recognize(a).should == [nil, a]
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flunkey::Helpers do
4
+ include Flunkey::Helpers
5
+
6
+ it "renders absolute url paths" do
7
+ root = Pathname.new('/some/tmp')
8
+ p1 = Pathname.new('/some/tmp/a/b.sh')
9
+ to_url(root, p1).should == '/tmp/a/b.sh'
10
+ to_url(root, root).should == '/tmp'
11
+ end
12
+
13
+ it "calculates breadcrumbs" do
14
+ root = Pathname.new('/some/tmp')
15
+ breadcrumbs(root, Pathname.new('/some/tmp/a/b')).should == [
16
+ Pathname.new('/some/tmp'),
17
+ Pathname.new('/some/tmp/a'),
18
+ Pathname.new('/some/tmp/a/b')
19
+ ]
20
+ breadcrumbs(root, Pathname.new('/some/tmp')).should == [
21
+ Pathname.new('/some/tmp')
22
+ ]
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flunkey::Metadata do
4
+
5
+ it "parses single-line metadata" do
6
+ m = Flunkey::Metadata.parse <<-END
7
+ kMDItemCreator = "PDFpen"
8
+ kMDItemFSInvisible = 0
9
+ kMDItemFSNodeCount = (null)
10
+ kMDItemFSContentChangeDate = 2013-11-04 07:51:03 +0000
11
+ END
12
+ m.keys.size.should == 4
13
+ m['kMDItemCreator'].should == 'PDFpen'
14
+ m['kMDItemFSInvisible'].should == '0'
15
+ m['kMDItemFSNodeCount'].should be_nil
16
+ # treated as a date because the name ends in …Date
17
+ m['kMDItemFSContentChangeDate'].should === DateTime.new(2013, 11, 4, 7, 51, 3, '+0')
18
+ end
19
+
20
+ it "parses array metadata" do
21
+ m = Flunkey::Metadata.parse <<-END
22
+ kMDItemContentTypeTree = (
23
+ "public.data",
24
+ "public.item"
25
+ )
26
+ END
27
+ m.keys.size.should == 1
28
+ m['kMDItemContentTypeTree'].should be_a Array
29
+ m['kMDItemContentTypeTree'].should === %w{public.data public.item}
30
+ end
31
+
32
+ it "does the full format" do
33
+ m = Flunkey::Metadata.parse <<-END
34
+ kMDItemCreator = "PDFpen"
35
+ kMDItemContentTypeTree = (
36
+ "public.data",
37
+ "public.item"
38
+ )
39
+ kMDItemFSNodeCount = (null)
40
+ END
41
+ m.keys.size.should == 3
42
+ end
43
+
44
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flunkey::PathnameExtensions do
4
+ it "detects bundles" do
5
+ # Mac applications are bundles
6
+ Pathname.new('/Applications/Safari.app').bundle?.should be_true
7
+
8
+ # standard directories are not bundles
9
+ Pathname.new(File.dirname(__FILE__)).bundle?.should be_false
10
+
11
+ # Files are not bundles
12
+ Pathname.new(__FILE__).bundle?.should be_false
13
+ end
14
+
15
+ it "shows parent directories" do
16
+ Pathname.new('/tmp').is_parent_of?(Pathname.new('/tmp/a/b')).should be_true
17
+ Pathname.new('/foo').is_parent_of?(Pathname.new('/tmp/a/b')).should be_false
18
+ Pathname.new('/foo').is_parent_of?(Pathname.new('/foobar')).should be_false
19
+ Pathname.new('/foo').is_parent_of?(Pathname.new('/foo')).should be_false
20
+ end
21
+
22
+ it "filters children" do
23
+ root = Pathname.new File.expand_path('../..', File.dirname(__FILE__))
24
+ root.filtered_children.should_not include(root + '.DS_Store')
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+
7
+ require 'rack/test'
8
+ require 'flunkey'
9
+
10
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
11
+ RSpec.configure do |config|
12
+ config.include Rack::Test::Methods
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ config.run_all_when_everything_filtered = true
15
+ config.filter_run :focus
16
+
17
+ # Run specs in random order to surface order dependencies. If you find an
18
+ # order dependency and want to debug it, you can fix the order by providing
19
+ # the seed, which is printed after each run.
20
+ # --seed 1234
21
+ config.order = 'random'
22
+ end
@@ -0,0 +1,4 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class FlunkeyTest < Test::Unit::TestCase
4
+ end
@@ -0,0 +1,19 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+ Encoding.default_external = "UTF-8" if defined? Encoding
3
+
4
+ begin
5
+ require 'rack'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rack'
9
+ end
10
+
11
+ testdir = File.dirname(__FILE__)
12
+ $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir)
13
+
14
+ require 'rack/test'
15
+ require 'test/unit'
16
+
17
+ class Test::Unit::TestCase
18
+ include Rack::Test::Methods
19
+ end
@@ -0,0 +1,16 @@
1
+ <div class="navbar navbar-blue navbar-static-top">
2
+ <div class="container">
3
+
4
+ <div class="navbar-header">
5
+ <a class="navbar-brand" href="<%= url('/') %>"><%= "Flunkey #{Flunkey::VERSION}" %></a>
6
+ </div>
7
+
8
+ <div class="navbar-collapse collapse">
9
+ <form action="/_search" method="get" class="navbar-form navbar-right" role="search">
10
+ <label class="sr-only" for="query">Search term</label>
11
+ <input type="search" name="query" class="form-control" id="query" placeholder="Enter search" value="<%= @query %>">
12
+ </form>
13
+ </div>
14
+
15
+ </div>
16
+ </div>
@@ -0,0 +1,31 @@
1
+ <tr>
2
+ <td class="filename">
3
+ <% if path_item.browsable? %>
4
+ <a href="<%= to_url(root, path_item) %>">
5
+ <%= icon_for(path_item) %> <%= path_item.basename %>
6
+ </a>
7
+ <% else %>
8
+ <a href="<%= to_url(root, path_item) %>/info">
9
+ <%= icon_for(path_item) %> <%= path_item.basename %>
10
+ </a>
11
+ <% end %>
12
+ </td>
13
+ <td><%= number_to_human_size(path_item.size) unless path_item.directory? %></td>
14
+ <td><%= l path_item.mtime %></td>
15
+ <td>
16
+ <div class="btn-group">
17
+ <a href="<%= to_url(root, path_item) %>/info" class="btn btn-default btn-xs" title="Information">
18
+ <i class="fa fa-info"></i>
19
+ </a>
20
+ <a href="<%= to_url(root, path_item) %>" class="btn btn-default btn-xs" title="Download" <%= path_item.downloadable? ? nil : 'disabled' %>>
21
+ <i class="fa fa-download"></i>
22
+ </a>
23
+ <a href="<%= to_url(root, path_item) %>/zip" class="btn btn-default btn-xs" title="Compress as Zip" <%= path_item.compressable? ? nil : 'disabled' %>>
24
+ <i class="fa fa-suitcase"></i>
25
+ </a>
26
+ <a href="<%= to_url(root, path_item) %>/ql" class="btn btn-default btn-xs" title="Quicklook" <%= path_item.file? ? nil : 'disabled' %>>
27
+ <i class="fa fa-eye"></i>
28
+ </a>
29
+ </div>
30
+ </td>
31
+ </tr>
@@ -0,0 +1,19 @@
1
+ <table class="table table-hover">
2
+ <thead>
3
+ <tr>
4
+ <th>File</th>
5
+ </tr>
6
+ </thead>
7
+ <tbody>
8
+ <% @paths.each do |path| %>
9
+ <tr>
10
+ <td>
11
+ <a href="<%= url(path.basename.to_s) %>">
12
+ <i class="fa fa-folder"></i>
13
+ <%= path.basename %>
14
+ </a>
15
+ </td>
16
+ </tr>
17
+ <% end %>
18
+ </tbody>
19
+ </table>
@@ -0,0 +1,79 @@
1
+ <ol class="breadcrumb">
2
+ <li><a href="<%= url('/')%>">Home</a></li>
3
+ <% breadcrumbs(@root, @path).each do |b| %>
4
+ <% if b==@path %>
5
+ <li class="active"><%= b.basename %></li>
6
+ <% else %>
7
+ <li><a href="<%= to_url(@root, b) %>"><%= b.basename %></a></li>
8
+ <% end %>
9
+ <% end %>
10
+ </ol>
11
+
12
+ <div class="page-header">
13
+ <h1><%= @path.basename %> <small> in <a href="<%= to_url(@root, @path.parent) %>"><%= to_url(@root, @path.parent) %></a></small></h1>
14
+ </div>
15
+
16
+ <div class="row">
17
+ <div class="col-md-4">
18
+ <img src="http://dummyimage.com/256x256/cccccc/0011ff" class="center-block">
19
+ <hr>
20
+ <p>
21
+ <a href="<%= to_url(@root, @path) %>" class="btn btn-primary" title="Download" <%= @path.downloadable? ? nil : 'disabled' %>>
22
+ <i class="fa fa-download"></i>
23
+ Download
24
+ </a>
25
+ </p>
26
+ <h5>Download estimates <small>in hh:mm:ss</small></h4>
27
+ <dl class="dl-horizontal">
28
+ <% download_estimates(@path.size).each do |name, duration| %>
29
+ <dt><%= name %></dt>
30
+ <dd><%= Time.at(duration).utc.strftime("%H:%M:%S") %></dd>
31
+ <% end %>
32
+ </dl>
33
+ </div>
34
+
35
+ <div class="col-md-8">
36
+ <div class="panel panel-default">
37
+ <div class="panel-heading">File metadata</div>
38
+ <div class="panel-body">
39
+ <p></p>
40
+ </div>
41
+ <table class="table">
42
+ <colgroup>
43
+ <col width="180">
44
+ <col >
45
+ </colgroup>
46
+ <tbody>
47
+ <tr>
48
+ <th>Name</th>
49
+ <td><%= @path.metadata['kMDItemDisplayName'] %></td>
50
+ </tr>
51
+ <tr>
52
+ <th>Type</th>
53
+ <td><%= @path.metadata['kMDItemKind'] %></td>
54
+ </tr>
55
+ <tr>
56
+ <th>Size</th>
57
+ <td><%= @path.metadata['kMDItemFSSize'] %></td>
58
+ </tr>
59
+ <tr>
60
+ <th>Created at</th>
61
+ <td><%= l @path.metadata['kMDItemContentCreationDate'] %></td>
62
+ </tr>
63
+ <tr>
64
+ <th>Added at</th>
65
+ <td><%= l @path.metadata['kMDItemDateAdded'] %></td>
66
+ </tr>
67
+ <tr>
68
+ <th>Modified at</th>
69
+ <td><%= l @path.metadata['kMDItemContentModificationDate'] %></td>
70
+ </tr>
71
+ <tr>
72
+ <th>Finder comment</th>
73
+ <td><%= @path.metadata['kMDItemFinderComment'] %></td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ </div>
79
+ </div>
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3
+ <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4
+ <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5
+ <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6
+ <head>
7
+ <meta charset="utf-8">
8
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9
+ <title><%= title %></title>
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
+ <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.1/css/bootstrap.min.css" rel="stylesheet">
12
+ <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.1/css/font-awesome.min.css" rel="stylesheet">
13
+ <link href="/application.css" rel="stylesheet">
14
+ </head>
15
+ <body>
16
+ <%= partial :navbar %>
17
+ <div class="container">
18
+ <%= yield %>
19
+
20
+ <hr>
21
+
22
+ <footer>
23
+ <p>Powered by Flunkey <%= Flunkey::VERSION %></p>
24
+ </footer>
25
+ </div>
26
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
27
+ <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.1/js/bootstrap.min.js"></script>
28
+ </body>
29
+ </html>
@@ -0,0 +1,3 @@
1
+ <div class="alert alert-danger">
2
+ <strong>File not found!</strong> Best check yo self, you're not looking too good.
3
+ </div>
@@ -0,0 +1,40 @@
1
+ <ol class="breadcrumb">
2
+ <li><a href="<%= url('/')%>">Home</a></li>
3
+ <% breadcrumbs(@root, @path).each do |b| %>
4
+ <% if b==@path %>
5
+ <li class="active"><%= b.basename %></li>
6
+ <% else %>
7
+ <li><a href="<%= to_url(@root, b) %>"><%= b.basename %></a></li>
8
+ <% end %>
9
+ <% end %>
10
+ </ol>
11
+
12
+ <table class="table table-hover">
13
+ <colgroup>
14
+ <col></col>
15
+ <col width="120"></col>
16
+ <col width="180"></col>
17
+ <col width="120"></col>
18
+ </colgroup>
19
+ <thead>
20
+ <tr>
21
+ <th>File</th>
22
+ <th>Size</th>
23
+ <th>Modified</th>
24
+ <th></th>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <tr>
29
+ <td colspan=4>
30
+ <a href="<%= to_url(@root, @path.parent) %>">
31
+ <i class="fa fa-arrow-left"></i>
32
+ ..
33
+ </a>
34
+ </td>
35
+ </tr>
36
+ </tbody>
37
+ <tbody>
38
+ <%= partial :path_item, collection:@path.filtered_children, locals:{ root:@root } %>
39
+ </tbody>
40
+ </table>
@@ -0,0 +1,32 @@
1
+ <ol class="breadcrumb">
2
+ <li><a href="<%= url('/')%>">Home</a></li>
3
+ <li class="active">Search results for &quot;<%= @query %>&quot;</li>
4
+ </ol>
5
+
6
+ <table class="table table-hover">
7
+ <colgroup>
8
+ <col></col>
9
+ <col width="120"></col>
10
+ <col width="180"></col>
11
+ <col width="120"></col>
12
+ </colgroup>
13
+ <thead>
14
+ <tr>
15
+ <th>File</th>
16
+ <th>Size</th>
17
+ <th>Modified</th>
18
+ <th></th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <% if @results.empty? %>
23
+ <tr>
24
+ <td colspan="4">No results for &quot;<%= @query %>&quot;</td>
25
+ </tr>
26
+ <% else %>
27
+ <% @results.each do |root, path| %>
28
+ <%= partial :path_item, locals:{ root:root, path_item:path }%>
29
+ <% end %>
30
+ <% end %>
31
+ </tbody>
32
+ </table>
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flunkey
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Christian Aust
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra-contrib
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sinatra-partial
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Flunkey is a minimal download portal.
98
+ email:
99
+ - git@kontakt.software-consultant.net
100
+ executables:
101
+ - flunkey
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - .gitignore
106
+ - .rspec
107
+ - Gemfile
108
+ - Gemfile.lock
109
+ - LICENSE
110
+ - README.md
111
+ - Rakefile
112
+ - bin/flunkey
113
+ - flunkey.gemspec
114
+ - lib/flunkey.rb
115
+ - lib/flunkey/application.rb
116
+ - lib/flunkey/functions.rb
117
+ - lib/flunkey/helpers.rb
118
+ - lib/flunkey/metadata.rb
119
+ - lib/flunkey/pathname_extensions.rb
120
+ - lib/flunkey/server.rb
121
+ - lib/flunkey/version.rb
122
+ - public/application.css
123
+ - spec/flunkey/functions_spec.rb
124
+ - spec/flunkey/helpers_spec.rb
125
+ - spec/flunkey/metadata_spec.rb
126
+ - spec/flunkey/pathname_extensions_spec.rb
127
+ - spec/spec_helper.rb
128
+ - test/flunkey_test.rb
129
+ - test/helper.rb
130
+ - views/_navbar.erb
131
+ - views/_path_item.erb
132
+ - views/index.erb
133
+ - views/info.erb
134
+ - views/layout.erb
135
+ - views/not_found.erb
136
+ - views/path.erb
137
+ - views/search.erb
138
+ homepage: ''
139
+ licenses:
140
+ - MIT
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 2.0.6
159
+ signing_key:
160
+ specification_version: 4
161
+ summary: Flunkey is a minimal download portal.
162
+ test_files:
163
+ - spec/flunkey/functions_spec.rb
164
+ - spec/flunkey/helpers_spec.rb
165
+ - spec/flunkey/metadata_spec.rb
166
+ - spec/flunkey/pathname_extensions_spec.rb
167
+ - spec/spec_helper.rb
168
+ - test/flunkey_test.rb
169
+ - test/helper.rb