Class: Colore::Document

Inherits:
Object
  • Object
show all
Defined in:
lib/document.rb

Overview

This is a representation of the document stored on disk. Each doc is stored in its own directory, which has this structure:

(doc_id) ┬─ metadata.json
         ├─ title
         ├─ current → v002
         ├─ v001 ─┬─ foo.docx
         │        └─ foo.pdf
         └─ v002 ─┬─ foo.docx
                  └─ foo.jpg

Constant Summary collapse

CURRENT =
'current'
AUTHOR_FILE =
'_author.txt'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_dir, doc_key) ⇒ Document

Constructor.

Parameters:

  • base_dir (String)

    The base path to the storage area

  • doc_key (DocKey)

    The document identifier


74
75
76
77
# File 'lib/document.rb', line 74

def initialize base_dir, doc_key
  @base_dir = base_dir
  @doc_key = doc_key
end

Instance Attribute Details

#base_dirObject (readonly)

Returns the value of attribute base_dir


20
21
22
# File 'lib/document.rb', line 20

def base_dir
  @base_dir
end

#doc_keyObject (readonly)

Returns the value of attribute doc_key


21
22
23
# File 'lib/document.rb', line 21

def doc_key
  @doc_key
end

Class Method Details

.create(base_dir, doc_key) ⇒ Document

Creates the document directory. Raises [DocumentExists] if the document already exists.

Parameters:

  • base_dir (String)

    The base path to the storage area

  • doc_key (DocKey)

    The document identifier

Returns:

Raises:


46
47
48
49
50
51
# File 'lib/document.rb', line 46

def self.create base_dir, doc_key
  doc_dir = directory base_dir, doc_key
  raise DocumentExists.new if File.exist? doc_dir
  FileUtils.mkdir_p doc_dir
  self.new base_dir, doc_key
end

.delete(base_dir, doc_key) ⇒ void

This method returns an undefined value.

Deletes the document directory (and all contents) if it exists.

Parameters:

  • base_dir (String)

    The base path to the storage area

  • doc_key (DocKey)

    The document identifier


66
67
68
69
# File 'lib/document.rb', line 66

def self.delete base_dir, doc_key
  return unless exists? base_dir, doc_key
  FileUtils.rm_rf directory( base_dir, doc_key )
end

.directory(base_dir, doc_key) ⇒ Pathname

Returns document directory path

Parameters:

  • base_dir (String)

    The base path to the storage area

  • doc_key (DocKey)

    The document identifier

Returns:

  • (Pathname)

30
31
32
# File 'lib/document.rb', line 30

def self.directory base_dir, doc_key
  Pathname.new(base_dir) + doc_key.path
end

.exists?(base_dir, doc_key) ⇒ Bool

Returns true if the document exists

Parameters:

  • base_dir (String)

    The base path to the storage area

  • doc_key (DocKey)

    The document identifier

Returns:

  • (Bool)

38
39
40
# File 'lib/document.rb', line 38

def self.exists? base_dir, doc_key
  File.exist? directory(base_dir, doc_key)
end

.load(base_dir, doc_key) ⇒ Document

Loads the document information. Raises [DocumentNotFound] if the document does not exist.

Parameters:

  • base_dir (String)

    The base path to the storage area

  • doc_key (DocKey)

    The document identifier

Returns:

Raises:


57
58
59
60
# File 'lib/document.rb', line 57

def self.load base_dir, doc_key
  raise DocumentNotFound.new unless exists? base_dir, doc_key
  doc = self.new base_dir, doc_key
end

Instance Method Details

#add_file(version, filename, body, author = nil) ⇒ void

This method returns an undefined value.

Adds the given file under the specified version.

Parameters:

  • version (String)

    the version identifier (can be 'current')

  • filename (String)

    the name of the file

  • body (String or IO)

    the file contents (binary string or IO)

  • author (String) (defaults to: nil)

    the author of the file (optional)

Raises:


138
139
140
141
142
143
# File 'lib/document.rb', line 138

def add_file version, filename, body, author=nil
  raise VersionNotFound.new unless File.exist?( directory + version )
  body = StringIO.new(body) unless body.respond_to?(:read) # string -> IO
  File.open( directory + version + filename, "wb" ) { |f| IO.copy_stream(body,f) }
  File.open( directory + version + AUTHOR_FILE, 'w' ) { |f| f.write author }
end

#current_versionObject

Returns the identifier of the current version.


108
109
110
# File 'lib/document.rb', line 108

def current_version
  (directory + CURRENT).realpath.basename.to_s
end

#delete_version(version) ⇒ Object

Deletes the given version, including its files.

Raises:


155
156
157
158
159
160
# File 'lib/document.rb', line 155

def delete_version version
  return unless File.exist?( directory + version )
  raise VersionIsCurrent.new if version == CURRENT
  raise VersionIsCurrent.new if (directory + CURRENT).realpath == (directory+version).realpath
  FileUtils.rm_rf( directory + version )
end

#directoryObject

Returns the document storage directory.

Returns:

  • the document storage directory.


80
81
82
# File 'lib/document.rb', line 80

def directory
  self.class.directory @base_dir, @doc_key
end

#file_path(version, filename) ⇒ Object

Returns the URL query path for the given file. This can be used to construct a full URL to the file, for example:

f = "http://colore:1234/#{doc.file_path 'v001', 'fred.docx'}"

167
168
169
170
171
# File 'lib/document.rb', line 167

def file_path version, filename  # TODO: don't like this hard-code
  # it should really be in the app, but the hash is generated here

  "/document/#{@doc_key.app}/#{@doc_key.doc_id}/#{version}/#{filename}"
end

#get_file(version, filename) ⇒ String

Retrieves the requested file

Parameters:

  • version (String)

    the version identifier

  • filename (String)

    the name of the file

Returns:

  • (String)

    mime type

  • (String)

    the file body

Raises:


178
179
180
181
182
183
# File 'lib/document.rb', line 178

def get_file version, filename
  path = directory + version + filename
  raise FileNotFound unless File.exist? path
  body = File.read path
  return body.mime_type, body
end

#has_version?(version) ⇒ Boolean

Returns true if the document has the specified version.

Returns:

  • (Boolean)

103
104
105
# File 'lib/document.rb', line 103

def has_version? version
  versions.include?(version) || version == CURRENT
end

#new_version(&block) ⇒ Object

Creates a new version, ready to store documents in Work is performed in a flock block to avoid concurrent race condition


120
121
122
123
124
125
126
127
128
129
130
# File 'lib/document.rb', line 120

def new_version &block
  lockfile = directory + '.lock'
  nvn = nil
  lockfile.open 'w' do |f|
    f.flock File::LOCK_EX # lock is auto-released at end of block
    nvn = next_version_number
    Dir.mkdir directory + nvn
    yield nvn if block_given?
  end
  nvn
end

#next_version_numberObject

Returns the next version number (which would be created with [#new_version]).


113
114
115
116
# File 'lib/document.rb', line 113

def next_version_number
  v_no = (versions.last || 'v000').gsub(/v/,'').to_i + 1
  "v%03d"%[v_no]
end

#save_metadataObject

Saves the document metadata to (doc-dir)/metadata.json This metadata is just the #to_hash, as JSON, and is intended for access by client applications. It is not used by Colore for anything.


218
219
220
221
222
# File 'lib/document.rb', line 218

def 
  File.open( directory + 'metadata.json', "w" ) do |f|
    f.puts JSON.pretty_generate(to_hash)
  end
end

#set_current(version) ⇒ Object

Sets the specified version as current.

Raises:


146
147
148
149
150
151
152
# File 'lib/document.rb', line 146

def set_current version
  raise VersionNotFound.new unless File.exist?( directory + version )
  raise InvalidVersion.new unless version =~ /^v\d+$/  # need to do this, or ln_s will put the symlink *into* the old dir

  File.unlink directory + CURRENT if File.exist? directory + CURRENT
  FileUtils.ln_s version, directory + CURRENT, force: true
end

#titleObject

Returns the document title.

Returns:

  • the document title.


85
86
87
88
# File 'lib/document.rb', line 85

def title
  return '' unless File.exist?( directory + 'title' )
  File.read( directory + 'title' ).chomp
end

#title=(new_title) ⇒ Object

Sets the document title.


91
92
93
94
# File 'lib/document.rb', line 91

def title= new_title
  return if new_title.to_s.empty?
  File.open( directory + 'title', 'w' ) { |f| f.puts new_title  }
end

#to_hashObject

Summarises the document as a [Hash]


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/document.rb', line 186

def to_hash
  v_list = {}
  versions.each do |v|
    v_list[v] = {}
    Dir.glob(directory + v + '*').each do |file|
      pfile = Pathname.new(file)
      next if pfile.basename.to_s == AUTHOR_FILE
      content_type = File.read(pfile,200).mime_type
      author = File.read( pfile.parent + AUTHOR_FILE ).chomp rescue nil
      suffix = pfile.extname.gsub( /\./, '')
      next if suffix.empty?
      v_list[v][suffix] = {
        content_type: content_type,
        filename: pfile.basename.to_s,
        path: file_path(v,pfile.basename),
        author: author,
        created_at: pfile.mtime,
      }
    end
  end
  {
    app: @doc_key.app,
    doc_id: @doc_key.doc_id,
    title: title,
    current_version: current_version,
    versions: v_list,
  }
end

#versionsObject

Returns an array of the document version identifiers.


97
98
99
100
# File 'lib/document.rb', line 97

def versions
  versions = Dir.glob( directory + 'v*' )
  versions.reject { |v| ! v =~ /^v\d+$/ }.map{ |v| File.basename v }.sort
end