gallerist 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/CONTRIBUTING.md +53 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +25 -0
  6. data/README.md +57 -0
  7. data/Rakefile +16 -0
  8. data/assets/stylesheets/main.scss +17 -0
  9. data/bin/gallerist +59 -0
  10. data/config.ru +21 -0
  11. data/gallerist.gemspec +32 -0
  12. data/lib/gallerist.rb +46 -0
  13. data/lib/gallerist/app.rb +230 -0
  14. data/lib/gallerist/errors.rb +12 -0
  15. data/lib/gallerist/helpers.rb +72 -0
  16. data/lib/gallerist/library.rb +100 -0
  17. data/lib/gallerist/middleware/raise_warmup_exceptions.rb +24 -0
  18. data/lib/gallerist/middleware/show_exceptions.rb +40 -0
  19. data/lib/gallerist/models/admin_data.rb +10 -0
  20. data/lib/gallerist/models/album.rb +30 -0
  21. data/lib/gallerist/models/album_photo.rb +20 -0
  22. data/lib/gallerist/models/base_model.rb +32 -0
  23. data/lib/gallerist/models/image_proxies_model.rb +11 -0
  24. data/lib/gallerist/models/image_proxy_state.rb +22 -0
  25. data/lib/gallerist/models/master.rb +25 -0
  26. data/lib/gallerist/models/model_resource.rb +22 -0
  27. data/lib/gallerist/models/person.rb +46 -0
  28. data/lib/gallerist/models/person_model.rb +10 -0
  29. data/lib/gallerist/models/person_photo.rb +39 -0
  30. data/lib/gallerist/models/photo.rb +100 -0
  31. data/lib/gallerist/models/tag.rb +31 -0
  32. data/lib/gallerist/models/tag_photo.rb +20 -0
  33. data/lib/rack/handler/unicorn.rb +34 -0
  34. data/views/500.erb +5 -0
  35. data/views/album.erb +9 -0
  36. data/views/index.erb +24 -0
  37. data/views/layout.erb +34 -0
  38. data/views/partials/thumbnail.erb +13 -0
  39. data/views/person.erb +9 -0
  40. data/views/photos.erb +9 -0
  41. data/views/tag.erb +9 -0
  42. metadata +183 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ff2b65d33304727eae500b7fc80f7de3b925c9e9
4
+ data.tar.gz: 4725ef924d8903081662a61279c01fec06343499
5
+ SHA512:
6
+ metadata.gz: 16b0d2f8928d41365189e1159e59eae1a37b2256bfbfbd4c3aa28a0d87bc2a7405ba49d5d3c2c0d26876e7d5c7567b8de21d1349e16d043d9d0d350d6a2880f7
7
+ data.tar.gz: 8f3a100f666ffe5c626c9ca231b1b36cdbaaa402a52c83e0a105af6c3f676227bc07c159810923524b5e6ea84be02f2d0cd91c1dbbe4d9e68900511eed671352
@@ -0,0 +1,2 @@
1
+ pkg/
2
+ Gemfile.lock
@@ -0,0 +1,53 @@
1
+ Contribution Guidelines
2
+ =======================
3
+
4
+ First of all, each single contribution is appreciated, whether a typo fix,
5
+ improved documentation, a fixed bug or a whole new feature.
6
+
7
+ ## Making your changes
8
+
9
+ 1. Fork the repository on GitHub
10
+ 2. Create a topic branch with a descriptive name, e.g. `fix-issue-123` or
11
+ `feature-x`
12
+ 3. Make your modifications, complying with the
13
+ [code conventions](#code-conventions)
14
+ 4. Commit small logical changes, each with a descriptive commit message.
15
+ Please don˚t mix unrelated changes in a single commit.
16
+ 5. Don˚t commit things that are unrelated to the whole change.
17
+
18
+ ## Commit messages
19
+
20
+ Please format your commit messages as follows:
21
+
22
+ Short summary of the change (up to 50 characters)
23
+
24
+ Optionally add a more extensive description of your change after a
25
+ blank line. Wrap the lines in this and the following paragraphs after
26
+ 72 characters.
27
+
28
+ ## Submitting your changes
29
+
30
+ 1. Push your changes to a topic branch in your fork of the repository.
31
+ 2. [Submit a pull request][pr] to the original repository.
32
+ Describe your changes as short as possible, but as detailed as needed for
33
+ others to get an overview of your modifications.
34
+
35
+ ## Code conventions
36
+
37
+ * White spaces:
38
+ * Indent using 2 spaces
39
+ * Line endings must be line feeds (\n)
40
+ * Add a newline at the end of a file
41
+ * Name conventions:
42
+ * `UpperCamelCase` for classes and modules
43
+ * `lower_case` for variables and methods
44
+ * `UPPER_CASE` for constants
45
+
46
+ ## Further information
47
+
48
+ * [General GitHub documentation][gh-help]
49
+ * [GitHub pull request documentation][gh-pr]
50
+
51
+ [gh-help]: https://help.github.com
52
+ [gh-pr]: https://help.github.com/send-pull-requests
53
+ [pr]: https://github.com/koraktor/gallerist/pull/new
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2015, Sebastian Staudt
5
+
6
+ source 'https://rubygems.org'
7
+
8
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2015, Sebastian Staudt
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name of the author nor the names of its contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,57 @@
1
+ Gallerist
2
+ =========
3
+
4
+ Gallerist is a web application to browse libraries of Apple Photos and iPhoto.
5
+ It is built on top of [Sinatra][sinatra].
6
+
7
+ ## Requirements
8
+
9
+ * One or more Photos or iPhoto libraries (`.photoslibrary` or `.photolibrary`
10
+ directories)
11
+ * Ruby 2.x with Bundler
12
+
13
+ OS X 10.9 and above ship with Ruby 2.0. You will only have to install Bundler
14
+ (`gem install bundler`) to get started.
15
+
16
+ ## Installation
17
+
18
+ Gallerist can be simply installed as a Ruby gem.
19
+
20
+ ```shell
21
+ $ gem install gallerist
22
+ ```
23
+
24
+ *Note*: You might need to use `sudo` if you’re installing into your system
25
+ Ruby, e.g. when not using rbenv or RVM.
26
+
27
+ If you want to run the current development code please use Git to clone the
28
+ repository.
29
+
30
+ ## Usage
31
+
32
+ ```shell
33
+ $ bin/gallerist ~/Pictures/Photos\ Library.photoslibrary
34
+ ```
35
+
36
+ After that the application is served on port 9292 by default. You can open it
37
+ by simply browsing to `http://localhost:9292`.
38
+
39
+ Further command-line arguments are available, see `gallerist --help` for more
40
+ information.
41
+
42
+ ## Caveats
43
+
44
+ * iPhoto libraries work to a certain degree as iPhoto’s events are not
45
+ listed, only albums.
46
+ * Gallerist works on a copy of the library databases, i.e. changes to the
47
+ original library will not be reflected instantly. You will have to restart
48
+ the web app first.
49
+
50
+ ## Future plans
51
+
52
+ * Support for internal categories like photo stream and videos
53
+ * Support for moments and places
54
+ * Performance improvements
55
+
56
+ [brew]: http://brew.sh
57
+ [sinatra]: http://www.sinatrarb.com
@@ -0,0 +1,16 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2015, Sebastian Staudt
5
+
6
+ require 'rubygems/package_task'
7
+
8
+ spec = Gem::Specification.load 'gallerist.gemspec'
9
+ Gem::PackageTask.new spec do |pkg|
10
+ end
11
+
12
+ # Task to clean the package directory
13
+ desc 'Clean package directory'
14
+ task :clean do
15
+ FileUtils.rm_rf 'pkg'
16
+ end
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This code is free software; you can redistribute it and/or modify it under
3
+ * the terms of the new BSD License.
4
+ *
5
+ * Copyright (c) 2015, Sebastian Staudt
6
+ */
7
+
8
+ @import 'bootstrap';
9
+
10
+ .navbar-inverse .navbar-brand:hover {
11
+ color: $navbar-inverse-brand-color;
12
+ }
13
+
14
+ .tag {
15
+ display: inline-block;
16
+ margin-right: 1pt;
17
+ }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This code is free software; you can redistribute it and/or modify it under
4
+ # the terms of the new BSD License.
5
+ #
6
+ # Copyright (c) 2015, Sebastian Staudt
7
+
8
+ require 'optparse'
9
+ require 'rack'
10
+
11
+ ENV['RACK_ENV'] ||= 'production'
12
+
13
+ server_options = { port: 9292 }
14
+ opts = OptionParser.new do |opts|
15
+ opts.banner = 'Usage: gallerist [options] <library>'
16
+
17
+ opts.separator ''
18
+ opts.separator 'Options:'
19
+ opts.on('-p', '--port PORT', 'Specify on which port to listen') do |port|
20
+ server_options[:port] = port
21
+ end
22
+ opts.on('-d', '--debug', 'Enable debug mode') do
23
+ ENV['RACK_ENV'] = 'development'
24
+ end
25
+
26
+ opts.separator ''
27
+ opts.separator 'Arguments:'
28
+ opts.separator opts.summary_indent + '<library> The path to the photo library to open.'
29
+
30
+ opts.separator ''
31
+ opts.separator 'Advanced options:'
32
+ opts.on('-n', '--no-copy', 'Disable temporary copies of the library databases') do
33
+ ENV['GALLERIST_NOCOPY'] = 'true'
34
+ end
35
+ end
36
+ opts.parse!
37
+
38
+ $LOAD_PATH << File.join(__dir__, '..', 'lib')
39
+
40
+ require 'gallerist'
41
+
42
+ config_path = File.expand_path File.join(__dir__, '..', 'config.ru')
43
+
44
+ ENV['GALLERIST_LIBRARY'] = ARGV[0]
45
+
46
+ if ENV['GALLERIST_LIBRARY'].nil?
47
+ puts opts.help
48
+ exit 1
49
+ end
50
+
51
+ begin
52
+ Rack::Server.start config: config_path,
53
+ server: :unicorn,
54
+ Port: server_options[:port],
55
+ worker_processes: 4
56
+ rescue Gallerist::LibraryInUseError
57
+ $stderr.puts 'The library is currently in use.' <<
58
+ ' Is it currently opened in Photos?'
59
+ end
@@ -0,0 +1,21 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2015, Sebastian Staudt
5
+
6
+ unless defined? Gallerist
7
+ $LOAD_PATH << File.join(__dir__, 'lib')
8
+ require 'gallerist'
9
+ end
10
+
11
+ warmup do |app|
12
+ Rack::MockRequest.new(app).get '/',
13
+ 'rack.errors' => $stderr,
14
+ 'rack.warmup' => true
15
+ end
16
+
17
+ map '/assets' do
18
+ run Gallerist::App.sprockets
19
+ end
20
+
21
+ run Gallerist::App
@@ -0,0 +1,32 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2015, Sebastian Staudt
5
+
6
+ $LOAD_PATH << File.join(__dir__, 'lib')
7
+
8
+ require 'gallerist'
9
+
10
+ Gem::Specification.new do |s|
11
+ s.name = 'gallerist'
12
+ s.version = Gallerist::VERSION
13
+ s.platform = Gem::Platform::RUBY
14
+ s.authors = [ 'Sebastian Staudt' ]
15
+ s.email = [ 'koraktor@gmail.com' ]
16
+ s.homepage = 'https://github.com/koraktor/gallerist'
17
+ s.license = 'BSD'
18
+ s.summary = 'A web application to browse Apple Photos and iPhoto libraries'
19
+ s.description = 'View Photos and iPhoto libraries in your browser'
20
+
21
+ s.add_dependency 'activerecord', '~> 4.2'
22
+ s.add_dependency 'bootstrap-sass', '~> 3.3'
23
+ s.add_dependency 'rack', '~> 1.6'
24
+ s.add_dependency 'sinatra', '~> 1.4'
25
+ s.add_dependency 'sprockets-helpers', '~> 1.1'
26
+ s.add_dependency 'sqlite3', '~> 1.3'
27
+ s.add_dependency 'unicorn', '~> 4.8'
28
+
29
+ s.executables = [ 'gallerist' ]
30
+ s.files = `git ls-files`.split
31
+ s.require_paths = 'lib'
32
+ end
@@ -0,0 +1,46 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2015, Sebastian Staudt
5
+
6
+ module Gallerist
7
+
8
+ VERSION = '0.1.0'
9
+
10
+ autoload :App, 'gallerist/app'
11
+ autoload :Helpers, 'gallerist/helpers'
12
+ autoload :Library, 'gallerist/library'
13
+ autoload :LibraryInUseError, 'gallerist/errors'
14
+ autoload :RaiseWarmupExceptions, 'gallerist/middleware/raise_warmup_exceptions'
15
+ autoload :ShowExceptions, 'gallerist/middleware/show_exceptions'
16
+
17
+ # Models
18
+
19
+ MODELS = {}
20
+
21
+ def self.model(name, file)
22
+ autoload name, file
23
+
24
+ MODELS[name] = file
25
+ end
26
+
27
+ def self.load_models
28
+ MODELS.values.each { |file| require file }
29
+ end
30
+
31
+ model :AdminData, 'gallerist/models/admin_data'
32
+ model :Album, 'gallerist/models/album'
33
+ model :AlbumPhoto, 'gallerist/models/album_photo'
34
+ model :BaseModel, 'gallerist/models/base_model'
35
+ model :ImageProxiesModel, 'gallerist/models/image_proxies_model'
36
+ model :ImageProxyState, 'gallerist/models/image_proxy_state'
37
+ model :Master, 'gallerist/models/master'
38
+ model :ModelResource, 'gallerist/models/model_resource'
39
+ model :Person, 'gallerist/models/person'
40
+ model :PersonModel, 'gallerist/models/person_model'
41
+ model :PersonPhoto, 'gallerist/models/person_photo'
42
+ model :Photo, 'gallerist/models/photo'
43
+ model :Tag, 'gallerist/models/tag'
44
+ model :TagPhoto, 'gallerist/models/tag_photo'
45
+
46
+ end
@@ -0,0 +1,230 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2015, Sebastian Staudt
5
+
6
+ require 'active_record'
7
+ require 'bootstrap-sass'
8
+ require 'logger'
9
+ require 'sinatra/sprockets-helpers'
10
+
11
+ class Gallerist::App < Sinatra::Base
12
+
13
+ register Sinatra::Sprockets::Helpers
14
+
15
+ configure do
16
+ enable :logging
17
+
18
+ set :root, File.join(root, '..', '..')
19
+
20
+ set :library_path, ENV['GALLERIST_LIBRARY']
21
+ set :copy_dbs, !ENV['GALLERIST_NOCOPY']
22
+ set :views, File.join(root, 'views')
23
+
24
+ set :sprockets, Sprockets::Environment.new(root)
25
+
26
+ sprockets.append_path File.join(root, 'assets', 'stylesheets')
27
+ sprockets.cache = Sprockets::Cache::FileStore.new Dir.tmpdir
28
+ sprockets.css_compressor = :scss
29
+
30
+ configure_sprockets_helpers do |helpers|
31
+ helpers.debug = development?
32
+ end
33
+
34
+ Bootstrap.load!
35
+ end
36
+
37
+ configure :development do
38
+ set :logging, ::Logger::DEBUG
39
+
40
+ debug_logger = ::Logger.new($stdout, ::Logger::DEBUG)
41
+
42
+ sprockets.logger = debug_logger
43
+ ActiveRecord::Base.logger = debug_logger
44
+ end
45
+
46
+ error 500 do |error|
47
+ @title = 'Error'
48
+
49
+ raise error if env['rack.errors'].is_a? StringIO
50
+
51
+ erb :'500'
52
+ end
53
+
54
+ helpers Gallerist::Helpers
55
+
56
+ def send_library_file(file, options = {})
57
+ logger.debug "Serving file '%s' from library..." % [ file ]
58
+
59
+ file_path = File.join(library.path, file)
60
+ response = catch(:halt) { send_file file_path, options }
61
+ if response == 404
62
+ logger.error "File '%s' could not be served, because it does not exist." % file
63
+ end
64
+
65
+ halt response
66
+ end
67
+
68
+ def library
69
+ unless settings.respond_to? :library
70
+ settings.set :library, Gallerist::Library.new(settings.library_path)
71
+
72
+ logger.info "Loading library from \"#{library.path}\""
73
+
74
+ if settings.copy_dbs
75
+ logger.debug 'Creating temporary copy of the main library database...'
76
+ library.copy_base_db
77
+ logger.debug ' Completed.'
78
+ end
79
+
80
+ ActiveRecord::Base.establish_connection({
81
+ adapter: 'sqlite3',
82
+ database: library.library_db
83
+ })
84
+ ActiveRecord::Base.connection.exec_query 'PRAGMA journal_mode="MEMORY";'
85
+
86
+ if settings.copy_dbs
87
+ logger.debug 'Creating temporary copies of additional library databases...'
88
+ library.copy_extra_dbs
89
+ logger.debug ' Completed.'
90
+ end
91
+
92
+ logger.info " Found library with type '%s'." % [ library.app_id ]
93
+
94
+ Gallerist.load_models
95
+ Gallerist::BaseModel.descendants.each do |model|
96
+ logger.debug "Setting up %s for library type '%s'" % [ model, library.type ]
97
+
98
+ model.setup_for library.type
99
+ end
100
+
101
+ Gallerist::ImageProxiesModel.establish_connection({
102
+ adapter: 'sqlite3',
103
+ database: library.image_proxies_db
104
+ })
105
+ Gallerist::ImageProxiesModel.connection.exec_query 'PRAGMA journal_mode="MEMORY";'
106
+
107
+ Gallerist::PersonModel.establish_connection({
108
+ adapter: 'sqlite3',
109
+ database: library.person_db
110
+ })
111
+ Gallerist::PersonModel.connection.exec_query 'PRAGMA journal_mode="MEMORY";'
112
+ end
113
+ settings.library
114
+ end
115
+
116
+ def navbar_for(obj)
117
+ @navbar = [[ '/', library.name ]]
118
+
119
+ case obj
120
+ when :all_photos
121
+ @navbar << [ '/photos', 'All photos' ]
122
+ when :favorites
123
+ @navbar << [ '/favorites', 'Favorites' ]
124
+ when Gallerist::Album
125
+ @navbar << [ '/albums', 'Albums' ]
126
+ @navbar << [ '/albums/%s' % [ obj.id ], obj.name ]
127
+ when Gallerist::Person
128
+ @navbar << [ '/persons', 'Persons' ]
129
+ @navbar << [ '/persons/%s' % [ obj.id ], obj.name ]
130
+ when Gallerist::Tag
131
+ @navbar << [ '/tags', 'Tags' ]
132
+ @navbar << [ '/tags/%s' % [ obj.simple_name ], obj.name ]
133
+ end
134
+ end
135
+
136
+ def photo(id)
137
+ Gallerist::Photo.find id
138
+ rescue ActiveRecord::RecordNotFound
139
+ logger.error 'Could not find the photo with ID #%s.' % [ id ]
140
+ not_found
141
+ end
142
+
143
+ def self.setup_default_middleware(builder)
144
+ builder.use Sinatra::ExtendedRack
145
+ builder.use Gallerist::ShowExceptions if show_exceptions?
146
+ builder.use Gallerist::RaiseWarmupExceptions
147
+
148
+ setup_logging builder
149
+ end
150
+
151
+ get '/' do
152
+ @title = library.name
153
+
154
+ @albums = library.albums.visible.nonempty.order :date
155
+ @persons = library.persons
156
+ @tags = library.tags.nonempty.order 'photos_count desc'
157
+
158
+ navbar_for :root
159
+
160
+ erb :index
161
+ end
162
+
163
+ get '/albums/:id' do
164
+ @album = library.albums.find params[:id]
165
+ @title = @album.name
166
+
167
+ navbar_for @album
168
+
169
+ erb :album
170
+ end
171
+
172
+ get '/favorites' do
173
+ @photos = library.photos.favorites
174
+ @title = 'Favorites'
175
+
176
+ navbar_for :favorites
177
+
178
+ erb :photos
179
+ end
180
+
181
+ get '/persons/:id' do
182
+ @person = library.persons.find params[:id]
183
+ @title = @person.name
184
+
185
+ navbar_for @person
186
+
187
+ erb :person
188
+ end
189
+
190
+ get '/photos' do
191
+ @photos = library.photos
192
+ @title = 'All photos'
193
+
194
+ navbar_for :all_photos
195
+
196
+ erb :photos
197
+ end
198
+
199
+ get '/photos/:id' do
200
+ photo = photo params[:id]
201
+
202
+ send_library_file photo.image_path,
203
+ disposition: :inline,
204
+ filename: photo.file_name
205
+ end
206
+
207
+ get '/tags/:name' do
208
+ @tag = library.tags.find_by_simple_name params[:name]
209
+ @title = @tag.name
210
+
211
+ navbar_for @tag
212
+
213
+ erb :tag
214
+ end
215
+
216
+ get '/thumbs/:id' do
217
+ photo = photo params[:id]
218
+
219
+ if photo.thumbnail_available?
220
+ thumbnail_path = photo.small_thumbnail_path
221
+ else
222
+ thumbnail_path = photo.preview_path
223
+ end
224
+
225
+ send_library_file thumbnail_path,
226
+ disposition: :inline,
227
+ filename: 'thumb_%s' % [ photo.file_name ]
228
+ end
229
+
230
+ end