flunkey 0.0.1

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.
@@ -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