Feature #5248 » patch.diff
| lib/pstore.rb | ||
|---|---|---|
| 
     # 
   | 
||
| 
     # See PStore for documentation. 
   | 
||
| 
     require "fileutils" 
   | 
||
| 
     require "digest/md5" 
   | 
||
| 
     require "thread" 
   | 
||
| 
     # 
   | 
||
| 
     # PStore implements a file based persistence mechanism based on a Hash.  User 
   | 
||
| ... | ... | |
| 
       # Raises PStore::Error if the calling code is not in a PStore#transaction or 
   | 
||
| 
       # if the code is in a read-only PStore#transaction. 
   | 
||
| 
       # 
   | 
||
| 
       def in_transaction_wr() 
   | 
||
| 
         in_transaction() 
   | 
||
| 
       def in_transaction_wr 
   | 
||
| 
         in_transaction 
   | 
||
| 
         raise PStore::Error, "in read-only transaction" if @rdonly 
   | 
||
| 
       end 
   | 
||
| 
       private :in_transaction, :in_transaction_wr 
   | 
||
| ... | ... | |
| 
       # be read-only.  It will raise PStore::Error if called at any other time. 
   | 
||
| 
       # 
   | 
||
| 
       def []=(name, value) 
   | 
||
| 
         in_transaction_wr() 
   | 
||
| 
         in_transaction_wr 
   | 
||
| 
         @table[name] = value 
   | 
||
| 
       end 
   | 
||
| 
       # 
   | 
||
| ... | ... | |
| 
       # be read-only.  It will raise PStore::Error if called at any other time. 
   | 
||
| 
       # 
   | 
||
| 
       def delete(name) 
   | 
||
| 
         in_transaction_wr() 
   | 
||
| 
         in_transaction_wr 
   | 
||
| 
         @table.delete name 
   | 
||
| 
       end 
   | 
||
| ... | ... | |
| 
       # Constant for relieving Ruby's garbage collector. 
   | 
||
| 
       EMPTY_STRING = "" 
   | 
||
| 
       EMPTY_MARSHAL_DATA = Marshal.dump({}) 
   | 
||
| 
       EMPTY_MARSHAL_CHECKSUM = Digest::MD5.digest(EMPTY_MARSHAL_DATA) 
   | 
||
| 
       EMPTY_MARSHAL_CHECKSUM = EMPTY_MARSHAL_DATA.sum 
   | 
||
| 
       # 
   | 
||
| 
       # Open the specified filename (either in read-only mode or in 
   | 
||
| ... | ... | |
| 
         if read_only 
   | 
||
| 
           begin 
   | 
||
| 
             table = load(file) 
   | 
||
| 
             if !table.is_a?(Hash) 
   | 
||
| 
               raise Error, "PStore file seems to be corrupted." 
   | 
||
| 
             end 
   | 
||
| 
             raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash) 
   | 
||
| 
           rescue EOFError 
   | 
||
| 
             # This seems to be a newly-created file. 
   | 
||
| 
             table = {} 
   | 
||
| ... | ... | |
| 
             # This seems to be a newly-created file. 
   | 
||
| 
             table = {} 
   | 
||
| 
             checksum = empty_marshal_checksum 
   | 
||
| 
             size = empty_marshal_data.size 
   | 
||
| 
             size = empty_marshal_data.bytesize 
   | 
||
| 
           else 
   | 
||
| 
             table = load(data) 
   | 
||
| 
             checksum = Digest::MD5.digest(data) 
   | 
||
| 
             size = data.size 
   | 
||
| 
             if !table.is_a?(Hash) 
   | 
||
| 
               raise Error, "PStore file seems to be corrupted." 
   | 
||
| 
             end 
   | 
||
| 
             checksum = data.sum 
   | 
||
| 
             size = data.bytesize 
   | 
||
| 
             raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash) 
   | 
||
| 
           end 
   | 
||
| 
           data.replace(EMPTY_STRING) 
   | 
||
| 
           [table, checksum, size] 
   | 
||
| ... | ... | |
| 
       end 
   | 
||
| 
       def on_windows? 
   | 
||
| 
         is_windows = RUBY_PLATFORM =~ /mswin/  || 
   | 
||
| 
                      RUBY_PLATFORM =~ /mingw/  || 
   | 
||
| 
                      RUBY_PLATFORM =~ /bccwin/ || 
   | 
||
| 
                      RUBY_PLATFORM =~ /wince/ 
   | 
||
| 
         is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/ 
   | 
||
| 
         self.class.__send__(:define_method, :on_windows?) do 
   | 
||
| 
           is_windows 
   | 
||
| 
         end 
   | 
||
| 
         is_windows 
   | 
||
| 
       end 
   | 
||
| 
       # Check whether Marshal.dump supports the 'canonical' option. This option 
   | 
||
| 
       # makes sure that Marshal.dump always dumps data structures in the same order. 
   | 
||
| 
       # This is important because otherwise, the checksums that we generate may differ. 
   | 
||
| 
       def marshal_dump_supports_canonical_option? 
   | 
||
| 
         begin 
   | 
||
| 
           Marshal.dump(nil, -1, true) 
   | 
||
| 
           result = true 
   | 
||
| 
         rescue 
   | 
||
| 
           result = false 
   | 
||
| 
         end 
   | 
||
| 
         self.class.__send__(:define_method, :marshal_dump_supports_canonical_option?) do 
   | 
||
| 
           result 
   | 
||
| 
         end 
   | 
||
| 
         result 
   | 
||
| 
       end 
   | 
||
| 
       def save_data(original_checksum, original_file_size, file) 
   | 
||
| 
         # We only want to save the new data if the size or checksum has changed. 
   | 
||
| 
         # This results in less filesystem calls, which is good for performance. 
   | 
||
| 
         if marshal_dump_supports_canonical_option? 
   | 
||
| 
           new_data = Marshal.dump(@table, -1, true) 
   | 
||
| 
         else 
   | 
||
| 
           new_data = dump(@table) 
   | 
||
| 
         end 
   | 
||
| 
         new_checksum = Digest::MD5.digest(new_data) 
   | 
||
| 
         new_data = dump(@table) 
   | 
||
| 
         if new_data.size != original_file_size || new_checksum != original_checksum 
   | 
||
| 
         if new_data.bytesize != original_file_size || new_data.sum != original_checksum 
   | 
||
| 
           if @ultra_safe && !on_windows? 
   | 
||
| 
             # Windows doesn't support atomic file renames. 
   | 
||
| 
             save_data_with_atomic_file_rename_strategy(new_data, file) 
   | 
||
| ... | ... | |
| 
       def save_data_with_fast_strategy(data, file) 
   | 
||
| 
         file.rewind 
   | 
||
| 
         file.truncate(0) 
   | 
||
| 
         file.write(data) 
   | 
||
| 
         file.truncate(data.bytesize) 
   | 
||
| 
       end 
   | 
||
| 
       # This method is just a wrapped around Marshal.dump 
   | 
||
| 
       # to allow subclass overriding used in YAML::Store. 
   | 
||
| 
       def dump(table)  # :nodoc: 
   | 
||
| ... | ... | |
| 
       def empty_marshal_data 
   | 
||
| 
         EMPTY_MARSHAL_DATA 
   | 
||
| 
       end 
   | 
||
| 
       def empty_marshal_checksum 
   | 
||
| 
         EMPTY_MARSHAL_CHECKSUM 
   | 
||
| 
       end 
   | 
||
| ... | ... | |
| 
     # :enddoc: 
   | 
||
| 
     if __FILE__ == $0 
   | 
||
| 
     if __FILE__ == $PROGRAM_NAME 
   | 
||
| 
       db = PStore.new("/tmp/foo") 
   | 
||
| 
       db.transaction do 
   | 
||
| 
         p db.roots 
   | 
||