1/* 2 * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 17 * 18 * Authored by Michal Hruby <michal.mhr@gmail.com> 19 * 20 */ 21 22namespace Synapse 23{ 24 public class LocatePlugin : Object, Activatable, ActionProvider 25 { 26 public bool enabled { get; set; default = true; } 27 28 public void activate () 29 { 30 31 } 32 33 public void deactivate () 34 { 35 36 } 37 38 private class LocateItem : SearchMatch 39 { 40 public int default_relevancy { get; set; default = MatchScore.INCREMENT_SMALL; } 41 42 // for SearchMatch interface 43 public override async Gee.List<Match> search (string query, 44 QueryFlags flags, 45 ResultSet? dest_result_set, 46 Cancellable? cancellable = null) throws SearchError 47 { 48 var q = Query (0, query, flags); 49 q.cancellable = cancellable; 50 ResultSet? results = yield plugin.locate (q); 51 dest_result_set.add_all (results); 52 53 return dest_result_set.get_sorted_list (); 54 } 55 56 private unowned LocatePlugin plugin; 57 58 public LocateItem (LocatePlugin plugin) 59 { 60 Object (has_thumbnail: false, 61 icon_name: "search", 62 title: _("Locate"), 63 description: _("Locate files with this name on the filesystem")); 64 this.plugin = plugin; 65 } 66 } 67 68 static void register_plugin () 69 { 70 PluginRegistry.get_default ().register_plugin ( 71 typeof (LocatePlugin), 72 _("Locate"), 73 _("Runs locate command to find files on the filesystem."), 74 "search", 75 register_plugin, 76 Environment.find_program_in_path ("locate") != null, 77 _("Unable to find \"locate\" binary") 78 ); 79 } 80 81 static construct 82 { 83 register_plugin (); 84 } 85 86 LocateItem action; 87 88 construct 89 { 90 action = new LocateItem (this); 91 } 92 93 public bool handles_unknown () 94 { 95 return true; 96 } 97 98 public async ResultSet? locate (Query q) throws SearchError 99 { 100 var our_results = QueryFlags.AUDIO | QueryFlags.DOCUMENTS 101 | QueryFlags.IMAGES | QueryFlags.UNCATEGORIZED | QueryFlags.VIDEO; 102 103 var common_flags = q.query_type & our_results; 104 // strip query 105 q.query_string = q.query_string.strip (); 106 // ignore short searches 107 if (common_flags == 0 || q.query_string.char_count () <= 1) return null; 108 109 q.check_cancellable (); 110 111 q.max_results = 256; 112 string regex = Regex.escape_string (q.query_string); 113 // FIXME: split pattern into words and search using --regexp? 114 string[] argv = {"locate", "-i", "-l", "%u".printf (q.max_results), 115 "*%s*".printf (regex.replace (" ", "*"))}; 116 117 Gee.Set<string> uris = new Gee.HashSet<string> (); 118 119 try 120 { 121 Pid pid; 122 int read_fd; 123 124 // FIXME: fork on every letter... yey! 125 Process.spawn_async_with_pipes (null, argv, null, 126 SpawnFlags.SEARCH_PATH, 127 null, out pid, null, out read_fd); 128 129 UnixInputStream read_stream = new UnixInputStream (read_fd, true); 130 DataInputStream locate_output = new DataInputStream (read_stream); 131 string? line = null; 132 133 Regex filter_re = new Regex ("/\\."); // hidden file/directory 134 do 135 { 136 line = yield locate_output.read_line_async (Priority.DEFAULT_IDLE, q.cancellable); 137 if (line != null) 138 { 139 if (filter_re.match (line)) continue; 140 var file = File.new_for_path (line); 141 uris.add (file.get_uri ()); 142 } 143 } while (line != null); 144 } 145 catch (Error err) 146 { 147 if (!q.is_cancelled ()) warning ("%s", err.message); 148 } 149 150 q.check_cancellable (); 151 152 var result = new ResultSet (); 153 154 foreach (string s in uris) 155 { 156 var fi = new Utils.FileInfo (s, typeof (UriMatch)); 157 yield fi.initialize (); 158 if (fi.match_obj != null && fi.file_type in q.query_type) 159 { 160 int relevancy = MatchScore.INCREMENT_SMALL; // FIXME: relevancy 161 if (fi.uri.has_prefix ("file:///home/")) relevancy += MatchScore.INCREMENT_MINOR; 162 result.add (fi.match_obj, relevancy); 163 } 164 q.check_cancellable (); 165 } 166 167 return result; 168 } 169 170 public ResultSet? find_for_match (ref Query q, Match match) 171 { 172 var our_results = QueryFlags.AUDIO | QueryFlags.DOCUMENTS 173 | QueryFlags.IMAGES | QueryFlags.UNCATEGORIZED | QueryFlags.VIDEO; 174 175 var common_flags = q.query_type & our_results; 176 // ignore short searches 177 if (common_flags == 0 || !(match is UnknownMatch)) return null; 178 179 // strip query 180 q.query_string = q.query_string.strip (); 181 bool query_empty = q.query_string == ""; 182 var results = new ResultSet (); 183 184 if (query_empty) 185 { 186 results.add (action, action.default_relevancy); 187 } 188 else 189 { 190 var matchers = Query.get_matchers_for_query (q.query_string, 0, 191 RegexCompileFlags.CASELESS); 192 foreach (var matcher in matchers) 193 { 194 if (matcher.key.match (action.title)) 195 { 196 results.add (action, matcher.value); 197 break; 198 } 199 } 200 } 201 202 return results; 203 } 204 } 205} 206