# -*- coding: utf-8 -*- # # @file # @brief Milkodeで使用するデータベース # @author ongaeshi # @date 2010/10/17 require 'rubygems' require 'pathname' require 'singleton' require 'groonga' require 'milkode/common/dbdir' require 'milkode/cdstk/yaml_file_wrapper' require 'milkode/database/groonga_database' require 'milkode/common/util' require 'milkode/database/updater' include Milkode module Milkode # @todo データベースアクセスは将来的にはGroongaDatabaseに収束させる class Database include Singleton attr_reader :documents @@db_dir = nil def self.setup(db_dir) @@db_dir = db_dir end def self.dbdir @@db_dir || Dbdir.default_dir end attr_reader :yaml attr_reader :grndb def initialize open end def yaml_reload # @yaml = YamlFileWrapper.load_if(@@db_dir || Dbdir.default_dir) end def open if !@grndb || @grndb.closed? open_force end end def open_force @grndb = GroongaDatabase.new @grndb.open(Database.dbdir) @grndb.yaml_sync(yaml_load.contents) @documents = @grndb.documents end def record(shortpath) DocumentRecord.create @documents.find_shortpath(shortpath) end def search(patterns, keywords, packages, current_path, fpaths, suffixs, fpath_or_packages, offset = 0, limit = -1) paths = [] strict_packages = [] is_not_search = false # パッケージ名未指定の時は現在位置を検索条件に追加 if packages.empty? && current_path != '' package, restpath = Util.divide_shortpath(current_path) grn_package = @grndb.packages[package] if grn_package # パッケージ名 strict_packages << package # ファイルパス directory = grn_package.directory if restpath paths << File.join(directory, restpath) else paths << directory end else is_not_search = true end end # 検索 records, total_records, result = [], 0, nil begin unless is_not_search records, total_records, result = @documents.search_with_match( :patterns => patterns, :keywords => keywords, :paths => paths, :packages => packages, :strict_packages => strict_packages, :restpaths => fpaths, :suffixs => suffixs, :fpath_or_packages => fpath_or_packages, :offset => offset, :limit => limit ) end rescue Groonga::TooLargeOffset end # 結果 return records.map{|r| DocumentRecord.new(r)}, total_records, result end def selectAll(current_path, offset, limit) if current_path == '' return @documents.select_all_sort_by_shortpath(offset, limit) end paths = [] strict_packages = [] is_not_search = false package, restpath = Util.divide_shortpath(current_path) grn_package = @grndb.packages[package] if grn_package # package name strict_packages << package # file path directory = grn_package.directory if restpath paths << File.join(directory, restpath) else paths << directory end else is_not_search = true end # search records, total_records, result = [], 0, nil begin unless is_not_search records, total_records, result = @documents.search_with_match( :paths => paths, :strict_packages => strict_packages, :offset => offset, :limit => limit ) end rescue Groonga::TooLargeOffset end if (limit != -1) records = records.sort_by{|record| DocumentRecord::shortpath(record).downcase } else records = records.sort_by{|record| DocumentRecord::shortpath(record).downcase } end return records, result.size end # レコード数を得る def totalRecords @documents.size end # 指定パッケージに属する全てのレコードを得る def package_records(name) @documents.package_records(name) end # yamlからパッケージの総数を得る # @todo PackageTableから取得するように変更する def yaml_package_num yaml_load.contents.size end def yaml_package(name) yaml_load.find_name(name) end # @sample test/test_database.rb:43 TestDatabase#t_fileList def fileList(base) base_parts = base.split("/") base_depth = base_parts.length # 'depth==0'の時はMilkodeYaml#contentsからファイルリストを生成して高速化 if (base_depth == 0) return yaml_load.contents.sort_by{|v| v.name}.map{|v| [v.name, false] } end # base/以下のファイルを全て取得 records = @documents.find_shortpath_below(base) # ファイルリストの生成 paths = records.map {|record| DocumentRecord.new(record).shortpath.split("/") }.find_all {|parts| # 先頭フォルダ名が一致するものをピックアップ parts.length > base_depth && parts[0, base_depth] == base_parts }.map {|parts| # [path, is_file] [parts[0, base_depth + 1].join("/"), parts.length == base_depth + 1] }.sort_by {|parts| # 配列の比較を利用したディレクトリ優先ソート # aaa, bbb/, aaa/, bbb -> [aaa/, bbb/, aaa, bbb] [parts[1] ? 1 : 0, parts[0].downcase] # [is_file(int), path(downcase)] }.uniq paths end def packages(sort_kind) sorted = nil if sort_kind == "favtime" sorted = @grndb.packages.favs elsif (sort_kind) sorted = @grndb.packages.sort(sort_kind) else # 大文字/小文字を無視してソートするため、速度を犠牲に # sorted = @grndb.packages.sort("name", "ascending") sorted = @grndb.packages.to_a.sort_by {|r| r.name.downcase} end sorted.map {|r| r.name} end def touch_viewtime(path) package, restpath = Util.divide_shortpath(path) @grndb.packages.touch_if(package, :viewtime) if package end def fav?(name) @grndb.packages.fav?(name) end def set_fav(name, favorited) time = favorited ? Time.now : Time.at(0) @grndb.packages.touch_if(name, :favtime, time) end def update(name) result = Updater::ResultAccumulator.new result << update_in(yaml_load.find_name(name)) result end def update_all result = Updater::ResultAccumulator.new yaml_load.contents.each do |package| result << update_in(package) end result end def self.validate? YamlFileWrapper.load_if(Database.dbdir) != nil end private def yaml_load YamlFileWrapper.load_if(Database.dbdir) end def update_in(package) updater = Updater.new(@grndb, package.name) yaml = yaml_load updater.set_global_gitignore(yaml.global_gitignore) if yaml.global_gitignore updater.set_package_ignore IgnoreSetting.new("/", package.ignore) updater.enable_no_auto_ignore if package.options[:no_auto_ignore] updater.enable_update_with_git_pull if package.options[:update_with_git_pull] updater.enable_update_with_svn_update if package.options[:update_with_svn_update] updater.enable_update_with_ctags if package.options[:update_with_ctags] updater.enable_update_with_ctags_e if package.options[:update_with_ctags_e] updater.exec updater.result end end end