Class: Hobix::Storage::FileSys

Inherits:
BaseStorage show all
Defined in:
lib/hobix/storage/filesys.rb

Overview

The FileSys class is a storage plugin, it manages the loading and dumping of Hobix entries and attachments. The FileSys class also keeps an index of entry information, to keep the system from loading unneeded entries.

Instance Method Summary collapse

Methods inherited from BaseStorage

#after, #all, #before, #default_entry, #default_entry_id, #inpath, #lastn, #match, #within

Methods inherited from BasePlugin

inherited, start

Constructor Details

#initialize(weblog) ⇒ FileSys

Start the storage plugin for the weblog passed in.



52
53
54
55
56
57
58
# File 'lib/hobix/storage/filesys.rb', line 52

def initialize( weblog )
    super( weblog )
    @updated = {}
    @basepath = weblog.entry_path
    @default_author = weblog.authors.keys.first
    @weblog = weblog
end

Instance Method Details

#append_to_attachment(entry_id, attachment_type, *items) ⇒ Object

Appends the given items to an entry attachment with the given type, and then saves the modified attachment. If an attachment of the given type does not exist, it will be created.



401
402
403
404
405
# File 'lib/hobix/storage/filesys.rb', line 401

def append_to_attachment( entry_id, attachment_type, *items )
    attachment = load_attached( entry_id, attachment_type ) rescue []
    attachment += items
    save_attached( entry_id, attachment_type, attachment )
end

#check_id(id) ⇒ Object

Determine if id is a valid entry identifier, untaint if so.



68
69
70
# File 'lib/hobix/storage/filesys.rb', line 68

def check_id( id )
    id.untaint if id.tainted? and id =~ /^[\w\/\\]+$/
end

#entry_path(id, ext = extension) ⇒ Object

Build an entry’s complete path based on its id. Optionally, extension ext can be used to find the path of attachments.



74
75
76
# File 'lib/hobix/storage/filesys.rb', line 74

def entry_path( id, ext = extension )
    File.join( @basepath, id.split( '/' ) ) + "." + ext
end

#extensionObject

The default extension for entries. Defaults to: yaml.



63
64
65
# File 'lib/hobix/storage/filesys.rb', line 63

def extension
    'yaml'
end

#find(search = {}) ⇒ Object

Find entries based on criteria from the search hash. Possible criteria include:

:after

Select entries created after a given Time.

:before

Select entries created before a given Time.

:inpath

Select entries contained within a path.

:match

Select entries with an id which match a Regexp.

:search

Fulltext search of entries for search words.

:lastn

Limit the search to include only a given number of entries.

This method returns an Array of IndexEntry objects for use in skel_* methods.



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/hobix/storage/filesys.rb', line 258

def find( search = {} )
    load_index
    _index = @index
    if _index.empty?
        e = default_entry( @default_author )
        @updated[e.id] = e.updated
        _index = {e.id => @weblog.index_class.new(e)}
    end
    # if search[:search]
    #     sr = @search_index.find_words( search[:search] )
    # end
    unless search[:all]
        ignore_test = nil
        ignored = @weblog.sections_ignored
        unless ignored.empty?
            ignore_test = /^(#{ ignored.collect { |i| Regexp.quote( i ) }.join( '|' ) })/
        end
    end
    entries = _index.collect do |id, entry|
                  skip = false
                  if ignore_test and not search[:all]
                      skip = entry.id =~ ignore_test
                  end
                  search.each do |skey, sval|
                      break if skip
                      skip = case skey
                             when :after
                                 entry.created < sval
                             when :before
                                 entry.created > sval
                             when :inpath
                                 entry.id.index( sval ) != 0
                             when :match
                                 not entry.id.match sval
                             # when :search
                             #     not sr.results[entry.id]
                             else
                                 false
                             end
                  end
                  if skip then nil else entry end
              end.compact
    entries.slice!( search[:lastn]..-1 ) if search[:lastn] and entries.length > search[:lastn]
    entries
end

#find_attached(id) ⇒ Object

Discovers attachments to an entry identified by id.



363
364
365
366
367
368
369
# File 'lib/hobix/storage/filesys.rb', line 363

def find_attached( id )
    check_id( id )
    Dir[ entry_path( id, '*' ) ].collect do |att|
        atp = att.match( /#{ Regexp::quote( id ) }\.(?!#{ extension }$)/ )
        atp.post_match if atp
    end.compact
end

#get_months(entries) ⇒ Object

Returns an Array of Arrays representing the months which contain entries (pass in an Array of IndexEntry objects).

See Hobix::Weblog.skel_month for an example of this method’s usage.



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/hobix/storage/filesys.rb', line 342

def get_months( entries )
    return [] if entries.empty?
    first_time = entries.collect { |e| e.created }.min
    last_time = entries.collect { |e| e.created }.max
    start = Time.mktime( first_time.year, first_time.month, 1 )
    stop = Time.mktime( last_time.year, last_time.month, last_time.day )
    months = []
    until start > stop
        next_year, next_month = start.year, start.month + 1
        if next_month > 12
            next_year += next_month / 12
            next_month %= 12
        end
        month_end = Time.mktime( next_year, next_month, 1 ) - 1
        months << [ start, month_end, start.strftime( "/%Y/%m/" ) ] unless find( :after => start, :before => month_end).empty?
        start = month_end + 1
    end
    months
end

#last_created(entries) ⇒ Object

Returns a Time object for the latest creation time for a group of entries (pass in an Array of IndexEntry objects).



322
323
324
325
326
# File 'lib/hobix/storage/filesys.rb', line 322

def last_created( entries )
    entries.collect do |entry|
        entry.created
    end.max
end

#last_modified(entries) ⇒ Object

Returns a Time object for the latest modified time for a group of entries (pass in an Array of IndexEntry objects).



314
315
316
317
318
# File 'lib/hobix/storage/filesys.rb', line 314

def last_modified( entries )
    entries.collect do |entry|
        entry.modified
    end.max
end

#last_updated(entries) ⇒ Object

Returns a Time object for the latest updated time for a group of entries (pass in an Array of IndexEntry objects).



306
307
308
309
310
# File 'lib/hobix/storage/filesys.rb', line 306

def last_updated( entries )
    entries.collect do |entry|
        updated( entry.id )
    end.max
end

#load_attached(id, ext) ⇒ Object

Loads an attachment to an entry identified by id. Entries can have any kind of YAML attachment, each which a specific extension.



373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/hobix/storage/filesys.rb', line 373

def load_attached( id, ext )
    check_id( id )
    @attach_cache ||= {}
    file_id = "#{ id }.#{ ext }"
    unless @attach_cache.has_key? file_id
        @attach_cache[id] = File.open( entry_path( id, ext ) ) do |f| 
            YAML::load( f )
        end
    else
        @attach_cache[id]
    end
end

#load_entry(id) ⇒ Object

Loads the entry object identified by id. Entries are cached for future loading.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/hobix/storage/filesys.rb', line 115

def load_entry( id )
    return default_entry( @default_author ) if id == default_entry_id
    load_index
    check_id( id )
    @entry_cache ||= {}
    unless @entry_cache.has_key? id
        entry_file = entry_path( id )
        e = Hobix::Entry::load( entry_file )
        e.id = id
        e.link = e.class.url_link e, @link, @weblog.central_ext
        e.updated = updated( id )
        unless e.created
            e.created = @index[id].created
            e.modified = @index[id].modified
            File.open( entry_file, 'w' ) { |f| YAML::dump( e, f ) }
        end
        @entry_cache[id] = e
    else
        @entry_cache[id]
    end
end

#load_indexObject

Load the internal index (saved at @entry_path/index.hobix) and refresh any timestamps which may be stale.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/hobix/storage/filesys.rb', line 154

def load_index
    return false if @index
    index_path = File.join( @basepath, 'index.hobix' )
    index = if File.exists? index_path
                YAML::load( File.open( index_path ) )
            else
                YAML::Omap::new
            end
    @index = YAML::Omap::new
    # load_search_index( index.length == 0 )

    modified = false
    index_fields = @weblog.index_class.properties.keys
    Find::find( @basepath ) do |path|
        path.untaint
        if FileTest.directory? path
            Find.prune if File.basename(path)[0] == ?.
        else
            entry_path = path.gsub( /^#{ Regexp::quote( @basepath ) }\/?/, '' )
            next if entry_path !~ /\.#{ Regexp::quote( extension ) }$/
            entry_paths = File.split( $` )
            entry_paths.shift if entry_paths.first == '.'
            entry_id = entry_paths.join( '/' )
            @updated[entry_id] = File.mtime( path )

            index_entry = nil
            if ( index.has_key? entry_id ) and !( index[entry_id].is_a? ::Time ) # pre-0.4 index format
                index_entry = index[entry_id]
            end
            ## we will (re)load the entry if:
            if not index_entry.respond_to?( :updated ) or # it's new
                    ( index_entry.updated != @updated[entry_id] ) # it's changed
                    # or index_fields.detect { |f| index_entry.send( f ).nil? } # index fields have been added
                    # or search_needs_update? index_entry # entry is old or not available in search db

                puts "++ Reloaded #{ entry_id }"
                efile = entry_path( entry_id )
                e = Hobix::Entry::load( efile )
                e.id = entry_id
                index_entry = @weblog.index_class.new( e, index_fields ) do |i|
                    i.updated = @updated[entry_id]
                end
                # catalog_search_entry( e )
                modified = true
            end
            index_entry.id = entry_id
            @index[entry_id] = index_entry
        end
    end
    sort_index( modified )
    true
end

#nowObject



60
# File 'lib/hobix/storage/filesys.rb', line 60

def now; Time.at( Time.now.to_i ); end

#path_storage(p) ⇒ Object

Returns a Hobix::Storage::FileSys object with its scope limited to entries inside a certain path p.



222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/hobix/storage/filesys.rb', line 222

def path_storage( p )
    return self if ['', '.'].include? p
    load_index
    path_storage = self.dup
    path_storage.instance_eval do
        @index = @index.dup.delete_if do |id, entry|
            if id.index( p ) != 0
                @updated.delete( p )
                true
            end
        end
    end
    path_storage
end

#save_attached(id, ext, e) ⇒ Object

Saves an attachment to an entry identified by id. The attachment e is saved with an extension ext.



388
389
390
391
392
393
394
395
396
# File 'lib/hobix/storage/filesys.rb', line 388

def save_attached( id, ext, e )
    check_id( id )
    File.open( entry_path( id, ext ), 'w' ) do |f|
      YAML::dump( e, f )
    end

    @attach_cache ||= {}
    @attach_cache[id] = e
end

#save_entry(id, e, create_category = false) ⇒ Object

Save the entry object e and identify it as id. The create_category flag will forcefully make the needed directories.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/hobix/storage/filesys.rb', line 87

def save_entry( id, e, create_category=false )
    load_index
    check_id( id )
    e.created ||= (@index.has_key?( id ) ? @index[id].created : now)
    path = entry_path( id )

    unless create_category and File.exists? @basepath
        FileUtils.makedirs File.dirname( path )
    end
    
    File.open( path, 'w' ) { |f| YAML::dump( e, f ) }

    @entry_cache ||= {}
    e.id = id
    e.link = e.class.url_link e, @link, @weblog.central_ext
    e.updated = e.modified = now
    @entry_cache[id] = e

    @index[id] = @weblog.index_class.new( e ) do |i|
        i.updated = e.updated
    end
    @updated[id] = e.updated
    # catalog_search_entry( e )
    sort_index( true )
    e
end

#sections(opts = nil) ⇒ Object

Returns an Array all ‘sections’, or directories which contain entries. If you have three entries: ‘news/article1’, ‘about/me’, and ‘news/misc/article2’, then you have three sections: ‘news’, ‘about’, ‘news/misc’.



240
241
242
243
244
# File 'lib/hobix/storage/filesys.rb', line 240

def sections( opts = nil )
    load_index
    hsh = {}
    @index.collect { |id, e| e.section_id }.uniq.sort
end

#sort_index(modified) ⇒ Object

Sorts the internal entry index (used by load_index.)



208
209
210
211
212
213
214
215
216
217
218
# File 'lib/hobix/storage/filesys.rb', line 208

def sort_index( modified )
    return unless @index
    index_path = File.join( @basepath, 'index.hobix' )
    @index.sort! { |x,y| y[1].created <=> x[1].created }
    if modified
        File.open( index_path, 'w' ) do |f|
          YAML::dump( @index, f )
        end
        # @search_index.dump
    end
end

#touch_entry(id) ⇒ Object

Brings an entry’s updated time current.



79
80
81
82
83
# File 'lib/hobix/storage/filesys.rb', line 79

def touch_entry( id )
    check_id( id )
    @updated[id] = Time.now
    FileUtils.touch entry_path( id )
end

#updated(entry_id) ⇒ Object

Returns a Time object representing the updated time for the entry identified by entry_id. Takes into account attachments which have been updated.



331
332
333
334
335
336
# File 'lib/hobix/storage/filesys.rb', line 331

def updated( entry_id )
    find_attached( entry_id ).inject( @updated[entry_id] ) do |max, ext|
        mtime = File.mtime( entry_path( entry_id, ext ) )
        mtime > max ? mtime : max
    end
end