pastebin - collaborative debugging

pastebin is a collaborative debugging tool allowing you to share and modify code snippets while chatting on IRC, IM or a message board.

This site is developed to XHTML and CSS2 W3C standards. If you see this paragraph, your browser does not support those standards and you need to upgrade. Visit WaSP for a variety of options.

Ruby pastebin - collaborative debugging tool View Help


Posted by Anonymous on Wed 26 Mar 17:57
report abuse | download | new post

  1. # = PStore -- Transactional File Storage for Ruby Objects
  2. #
  3. # pstore.rb -
  4. #   originally by matz
  5. #   documentation by Kev Jackson and James Edward Gray II
  6. #   improved by Hongli Lai
  7. #
  8. # See PStore for documentation.
  9.  
  10.  
  11. require "fileutils"
  12. require "digest/md5"
  13. require "thread"
  14.  
  15. #
  16. # PStore implements a file based persistance mechanism based on a Hash.  User
  17. # code can store hierarchies of Ruby objects (values) into the data store file
  18. # by name (keys).  An object hierarchy may be just a single object.  User code
  19. # may later read values back from the data store or even update data, as needed.
  20. #
  21. # The transactional behavior ensures that any changes succeed or fail together.
  22. # This can be used to ensure that the data store is not left in a transitory
  23. # state, where some values were upated but others were not.
  24. #
  25. # Behind the scenes, Ruby objects are stored to the data store file with
  26. # Marshal.  That carries the usual limitations.  Proc objects cannot be
  27. # marshalled, for example.
  28. #
  29. # == Usage example:
  30. #
  31. #  require "pstore"
  32. #  
  33. #  # a mock wiki object...
  34. #  class WikiPage
  35. #    def initialize( page_name, author, contents )
  36. #      @page_name = page_name
  37. #      @revisions = Array.new
  38. #      
  39. #      add_revision(author, contents)
  40. #    end
  41. #    
  42. #    attr_reader :page_name
  43. #    
  44. #    def add_revision( author, contents )
  45. #      @revisions << { :created  => Time.now,
  46. #                      :author   => author,
  47. #                      :contents => contents }
  48. #    end
  49. #    
  50. #    def wiki_page_references
  51. #      [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
  52. #    end
  53. #    
  54. #    # ...
  55. #  end
  56. #  
  57. #  # create a new page...
  58. #  home_page = WikiPage.new( "HomePage", "James Edward Gray II",
  59. #                            "A page about the JoysOfDocumentation..." )
  60. #  
  61. #  # then we want to update page data and the index together, or not at all...
  62. #  wiki = PStore.new("wiki_pages.pstore")
  63. #  wiki.transaction do  # begin transaction; do all of this or none of it
  64. #    # store page...
  65. #    wiki[home_page.page_name] = home_page
  66. #    # ensure that an index has been created...
  67. #    wiki[:wiki_index] ||= Array.new
  68. #    # update wiki index...
  69. #    wiki[:wiki_index].push(*home_page.wiki_page_references)
  70. #  end                   # commit changes to wiki data store file
  71. #  
  72. #  ### Some time later... ###
  73. #  
  74. #  # read wiki data...
  75. #  wiki.transaction(true) do  # begin read-only transaction, no changes allowed
  76. #    wiki.roots.each do |data_root_name|
  77. #      p data_root_name
  78. #      p wiki[data_root_name]
  79. #    end
  80. #  end
  81. #
  82. # == Transaction limitations:
  83. #
  84. # By default, file integrity is only ensured as long as the operating system
  85. # (and the underlying hardware) doesn't raise any unexpected I/O errors. If an
  86. # I/O error occurs while PStore is writing to its file, then the file will
  87. # become corrupted.
  88. #
  89. # You can prevent this by setting <em>pstore.ultra_safe_transactions = true</em>.
  90. # However, this results in a minor performance loss, and only works on platforms
  91. # that support atomic file renames. Please consult the documentation for
  92. # +ultra_safe_transactions+ for details.
  93. #
  94. # Needless to say, if you're storing valuable data with PStore, then you should
  95. # backup the PStore files from time to time.
  96. class PStore
  97.   binmode = defined?(File::BINARY) ? File::BINARY : 0
  98.   RDWR_ACCESS = File::RDWR | File::CREAT | binmode
  99.   RD_ACCESS = File::RDONLY | binmode
  100.   WR_ACCESS = File::WRONLY | File::CREAT | File::TRUNC | binmode
  101.  
  102.   # The error type thrown by all PStore methods.
  103.   class Error < StandardError
  104.   end
  105.  
  106.   # Whether PStore should do its best to prevent file corruptions, even when under
  107.   # unlikely-to-occur error conditions such as out-of-space conditions and other
  108.   # unusual OS filesystem errors. Setting this flag comes at the price in the form
  109.   # of a performance loss.
  110.   #
  111.   # This flag only has effect on platforms on which file renames are atomic (e.g.
  112.   # all POSIX platforms: Linux, MacOS X, FreeBSD, etc). The default value is false.
  113.   attr_accessor :ultra_safe_transactions
  114.  
  115.   #
  116.   # To construct a PStore object, pass in the _file_ path where you would like
  117.   # the data to be stored.
  118.   #
  119.   # PStore objects are always reentrant. But if _thread_safe_ is set to true,
  120.   # then it will become thread-safe at the cost of a minor performance hit.
  121.   #
  122.   def initialize(file, thread_safe = false)
  123.     dir = File::dirname(file)
  124.     unless File::directory? dir
  125.       raise PStore::Error, format("directory %s does not exist", dir)
  126.     end
  127.     if File::exist? file and not File::readable? file
  128.       raise PStore::Error, format("file %s not readable", file)
  129.     end
  130.     @transaction = false
  131.     @filename = file
  132.     @abort = false
  133.     @ultra_safe_transactions = false
  134.     if @thread_safe
  135.       @lock = Mutex.new
  136.     else
  137.       @lock = DummyMutex.new
  138.     end
  139.   end
  140.  
  141.   # Raises PStore::Error if the calling code is not in a PStore#transaction.
  142.   def in_transaction
  143.     raise PStore::Error, "not in transaction" unless @transaction
  144.   end
  145.   #
  146.   # Raises PStore::Error if the calling code is not in a PStore#transaction or
  147.   # if the code is in a read-only PStore#transaction.
  148.   #
  149.   def in_transaction_wr()
  150.     in_transaction()
  151.     raise PStore::Error, "in read-only transaction" if @rdonly
  152.   end
  153.   private :in_transaction, :in_transaction_wr
  154.  
  155.   #
  156.   # Retrieves a value from the PStore file data, by _name_.  The hierarchy of
  157.   # Ruby objects stored under that root _name_ will be returned.
  158.   #
  159.   # *WARNING*:  This method is only valid in a PStore#transaction.  It will
  160.   # raise PStore::Error if called at any other time.
  161.   #
  162.   def [](name)
  163.     in_transaction
  164.     @table[name]
  165.   end
  166.   #
  167.   # This method is just like PStore#[], save that you may also provide a
  168.   # _default_ value for the object.  In the event the specified _name_ is not
  169.   # found in the data store, your _default_ will be returned instead.  If you do
  170.   # not specify a default, PStore::Error will be raised if the object is not
  171.   # found.
  172.   #
  173.   # *WARNING*:  This method is only valid in a PStore#transaction.  It will
  174.   # raise PStore::Error if called at any other time.
  175.   #
  176.   def fetch(name, default=PStore::Error)
  177.     in_transaction
  178.     unless @table.key? name
  179.       if default == PStore::Error
  180.         raise PStore::Error, format("undefined root name `%s'", name)
  181.       else
  182.         return default
  183.       end
  184.     end
  185.     @table[name]
  186.   end
  187.   #
  188.   # Stores an individual Ruby object or a hierarchy of Ruby objects in the data
  189.   # store file under the root _name_.  Assigning to a _name_ already in the data
  190.   # store clobbers the old data.
  191.   #
  192.   # == Example:
  193.   #
  194.   #  require "pstore"
  195.   #  
  196.   #  store = PStore.new("data_file.pstore")
  197.   #  store.transaction do  # begin transaction
  198.   #    # load some data into the store...
  199.   #    store[:single_object] = "My data..."
  200.   #    store[:obj_heirarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
  201.   #                              "James Gray"  => ["erb.rb", "pstore.rb"] }
  202.   #  end                   # commit changes to data store file
  203.   #
  204.   # *WARNING*:  This method is only valid in a PStore#transaction and it cannot
  205.   # be read-only.  It will raise PStore::Error if called at any other time.
  206.   #
  207.   def []=(name, value)
  208.     in_transaction_wr()
  209.     @table[name] = value
  210.   end
  211.   #
  212.   # Removes an object hierarchy from the data store, by _name_.
  213.   #
  214.   # *WARNING*:  This method is only valid in a PStore#transaction and it cannot
  215.   # be read-only.  It will raise PStore::Error if called at any other time.
  216.   #
  217.   def delete(name)
  218.     in_transaction_wr()
  219.     @table.delete name
  220.   end
  221.  
  222.   #
  223.   # Returns the names of all object hierarchies currently in the store.
  224.   #
  225.   # *WARNING*:  This method is only valid in a PStore#transaction.  It will
  226.   # raise PStore::Error if called at any other time.
  227.   #
  228.   def roots
  229.     in_transaction
  230.     @table.keys
  231.   end
  232.   #
  233.   # Returns true if the supplied _name_ is currently in the data store.
  234.   #
  235.   # *WARNING*:  This method is only valid in a PStore#transaction.  It will
  236.   # raise PStore::Error if called at any other time.
  237.   #
  238.   def root?(name)
  239.     in_transaction
  240.     @table.key? name
  241.   end
  242.   # Returns the path to the data store file.
  243.   def path
  244.     @filename
  245.   end
  246.  
  247.   #
  248.   # Ends the current PStore#transaction, committing any changes to the data
  249.   # store immediately.
  250.   #
  251.   # == Example:
  252.   #
  253.   #  require "pstore"
  254.   #  
  255.   #  store = PStore.new("data_file.pstore")
  256.   #  store.transaction do  # begin transaction
  257.   #    # load some data into the store...
  258.   #    store[:one] = 1
  259.   #    store[:two] = 2
  260.   #  
  261.   #    store.commit        # end transaction here, committing changes
  262.   #  
  263.   #    store[:three] = 3   # this change is never reached
  264.   #  end
  265.   #
  266.   # *WARNING*:  This method is only valid in a PStore#transaction.  It will
  267.   # raise PStore::Error if called at any other time.
  268.   #
  269.   def commit
  270.     in_transaction
  271.     @abort = false
  272.     throw :pstore_abort_transaction
  273.   end
  274.   #
  275.   # Ends the current PStore#transaction, discarding any changes to the data
  276.   # store.
  277.   #
  278.   # == Example:
  279.   #
  280.   #  require "pstore"
  281.   #  
  282.   #  store = PStore.new("data_file.pstore")
  283.   #  store.transaction do  # begin transaction
  284.   #    store[:one] = 1     # this change is not applied, see below...
  285.   #    store[:two] = 2     # this change is not applied, see below...
  286.   #  
  287.   #    store.abort         # end transaction here, discard all changes
  288.   #  
  289.   #    store[:three] = 3   # this change is never reached
  290.   #  end
  291.   #
  292.   # *WARNING*:  This method is only valid in a PStore#transaction.  It will
  293.   # raise PStore::Error if called at any other time.
  294.   #
  295.   def abort
  296.     in_transaction
  297.     @abort = true
  298.     throw :pstore_abort_transaction
  299.   end
  300.  
  301.   #
  302.   # Opens a new transaction for the data store.  Code executed inside a block
  303.   # passed to this method may read and write data to and from the data store
  304.   # file.
  305.   #
  306.   # At the end of the block, changes are committed to the data store
  307.   # automatically.  You may exit the transaction early with a call to either
  308.   # PStore#commit or PStore#abort.  See those methods for details about how
  309.   # changes are handled.  Raising an uncaught Exception in the block is
  310.   # equivalent to calling PStore#abort.
  311.   #
  312.   # If _read_only_ is set to +true+, you will only be allowed to read from the
  313.   # data store during the transaction and any attempts to change the data will
  314.   # raise a PStore::Error.
  315.   #
  316.   # Note that PStore does not support nested transactions.
  317.   #
  318.   def transaction(read_only = false, &block)  # :yields:  pstore
  319.     value = nil
  320.     raise PStore::Error, "nested transaction" if @transaction
  321.     @lock.synchronize do
  322.       @rdonly = read_only
  323.       @transaction = true
  324.       @abort = false
  325.       file = open_and_lock_file(@filename, read_only)
  326.       if file
  327.         if @ultra_safe_transactions && file_renames_are_atomic?
  328.           value = run_transaction_with_atomic_file_rename_strategy(file, read_only, &block)
  329.         else
  330.           value = run_transaction_with_regular_strategy(file, read_only, &block)
  331.         end
  332.       else
  333.         # This can only occur if read_only == true.
  334.         @table = {}
  335.         catch(:pstore_abort_transaction) do
  336.           value = yield(self)
  337.         end
  338.       end
  339.     end
  340.   ensure
  341.     @transaction = false
  342.     value
  343.   end
  344.  
  345.   private
  346.   class DummyMutex
  347.     def synchronize
  348.       yield
  349.     end
  350.   end
  351.  
  352.   #
  353.   # Open the specified filename (either in read-only mode or in
  354.   # read-write mode) and lock it for reading or writing.
  355.   #
  356.   # The opened File object will be returned. If _read_only_ is true,
  357.   # and the file does not exist, then nil will be returned.
  358.   #
  359.   # All exceptions are propagated.
  360.   #
  361.   def open_and_lock_file(filename, read_only)
  362.     if read_only
  363.       begin
  364.         file = File.new(filename, RD_ACCESS)
  365.         begin
  366.           file.flock(File::LOCK_SH)
  367.           return file
  368.         rescue
  369.           file.close
  370.           raise
  371.         end
  372.       rescue Errno::ENOENT
  373.         return nil
  374.       end
  375.     else
  376.       file = File.new(filename, RDWR_ACCESS)
  377.       file.flock(File::LOCK_EX)
  378.       return file
  379.     end
  380.   end
  381.  
  382.   def file_renames_are_atomic?
  383.     # File renames are atomic on POSIX platforms. As far as I know, Windows is
  384.     # the only non-POSIX platform that Ruby runs on.
  385.     #
  386.     # See also the following URL for information on atomic file writes on Windows:
  387.     # http://blogs.msdn.com/adioltean/archive/2005/12/28/507866.aspx
  388.     # This looks way too complicated: performance would go down the drain if we
  389.     # implement this. Maybe it's a good idea to use TxF in the future, but at the
  390.     # moment very few people use Vista and we'd need to write yet-another native
  391.     # extension.
  392.     @@file_renames_are_atomic ||= \
  393.       RUBY_PLATFORM !~ /mswin/ &&
  394.       RUBY_PLATFORM !~ /mingw/ &&
  395.       RUBY_PLATFORM !~ /bbcwin/ &&
  396.       RUBY_PLATFORM !~ /wince/
  397.   end
  398.  
  399.   def run_transaction_with_atomic_file_rename_strategy(file, read_only)
  400.     value = nil
  401.    
  402.     # Load PStore data file.
  403.     begin
  404.       @table = Marshal.load(file)
  405.       if !@table.is_a?(Hash)
  406.         raise Error, "PStore file seems to be corrupted."
  407.       end
  408.     rescue EOFError
  409.       # This is a newly-created file.
  410.       @table = {}
  411.     end
  412.    
  413.     # Run the transaction block.
  414.     catch(:pstore_abort_transaction) do
  415.       value = yield(self)
  416.     end
  417.    
  418.     # Write new data back to PStore file, if necessary. We write to a temporary file,
  419.     # then rename that to the original file.
  420.     if !@abort && !read_only
  421.       temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
  422.       temp_file = File.new(temp_filename, WR_ACCESS)
  423.       begin
  424.         Marshal.dump(@table, temp_file)
  425.         File.rename(temp_filename, @filename)
  426.       rescue
  427.         File.unlink(temp_file) rescue nil
  428.         raise
  429.       ensure
  430.         temp_file.close
  431.       end
  432.     end
  433.   ensure
  434.     file.close
  435.     value
  436.   end
  437.  
  438.   def run_transaction_with_regular_strategy(file, read_only)
  439.     value = nil
  440.    
  441.     # Load PStore data file.
  442.     begin
  443.       @table = Marshal.load(file)
  444.       if !@table.is_a?(Hash)
  445.         raise Error, "PStore file seems to be corrupted."
  446.       end
  447.     rescue EOFError
  448.       # This is a newly-created file.
  449.       @table = {}
  450.     end
  451.    
  452.     # Run the transaction block.
  453.     catch(:pstore_abort_transaction) do
  454.       value = yield(self)
  455.     end
  456.    
  457.     # Write new data back to PStore file, if necessary.
  458.     if !@abort && !read_only
  459.       # We must write the marshal data to a temporary buffer,
  460.       # because Marshal.dump might fail and we don't want to
  461.       # corrupt the file.
  462.       data = Marshal.dump(@table)
  463.       file.rewind
  464.       file.truncate(0)
  465.       file.write(data)
  466.       # Free the marshal data; this is good for the garbage collector.
  467.       data.replace("")
  468.     end
  469.   ensure
  470.     file.close
  471.     value
  472.   end
  473. end
  474.  
  475. # :enddoc:
  476.  
  477. if __FILE__ == $0
  478.   db = PStore.new("/tmp/foo")
  479.   db.transaction do
  480.     p db.roots
  481.     ary = db["root"] = [1,2,3,4]
  482.     ary[1] = [1,1.5]
  483.   end
  484.  
  485.   1000.times do
  486.     db.transaction do
  487.       db["root"][0] += 1
  488.       p db["root"][0]
  489.     end
  490.   end
  491.  
  492.   db.transaction(true) do
  493.     p db["root"]
  494.   end
  495. end

Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:

To highlight particular lines, prefix each line with @@


Remember me