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 )
    @modified = {}
    @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.



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

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.



257
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
# File 'lib/hobix/storage/filesys.rb', line 257

def find( search = {} )
    load_index
    _index = @index
    if _index.empty?
        e = default_entry( @default_author )
        @modified[e.id] = e.modified
        _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.



353
354
355
356
357
358
359
# File 'lib/hobix/storage/filesys.rb', line 353

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.



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/hobix/storage/filesys.rb', line 332

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/" ) ]
        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).



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

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



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

def last_modified( entries )
    entries.collect do |entry|
        modified( 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.



363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/hobix/storage/filesys.rb', line 363

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.



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

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.modified = modified( id )
        unless e.created
            e.created = @index[id].created
            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.



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
# File 'lib/hobix/storage/filesys.rb', line 155

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( '/' )
            @modified[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?( :modified ) or # it's new
                    ( index_entry.modified != @modified[entry_id] ) or # it's changed
                    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

                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.modified = @modified[entry_id]
                end
                # catalog_search_entry( e )
                modified = true
            end
            @index[index_entry.id] = index_entry
        end
    end
    sort_index( modified )
    true
end

#modified(entry_id) ⇒ Object

Returns a Time object representing the modified time for the entry identified by entry_id.



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

def modified( entry_id )
    find_attached( entry_id ).inject( @modified[entry_id] ) do |max, ext|
        mtime = File.mtime( entry_path( entry_id, ext ) )
        mtime > max ? mtime : max
    end
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.



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

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



378
379
380
381
382
383
384
385
386
# File 'lib/hobix/storage/filesys.rb', line 378

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
113
114
# 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 )

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

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

    @index[id] = @weblog.index_class.new( e ) do |i|
        i.modified = e.modified
    end
    @modified[id] = e.modified
    # 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’.



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

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



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

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 modified time current.



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

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