summaryrefslogtreecommitdiff
path: root/src/librygel-core/rygel-recursive-module-loader.vala
blob: 9dfa2301a9a393c9b91434d0790b35cea68da488 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/*
 * Copyright (C) 2012 Intel Corporation.
 *
 * Author: Jens Georg <jensg@openismus.com>
 *
 * This file is part of Rygel.
 *
 * Rygel is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Rygel is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/**
 * Recursively walk a folder looking for shared libraries.
 *
 * The folder can either be walked synchronously or asynchronously.
 * Implementing classes need to implement the abstract method
 * load_module_from_file() which is called when the walker encounters a
 * dynamic module file.
 */
public abstract class Rygel.RecursiveModuleLoader : Object {
    private const string LOADER_ATTRIBUTES =
                            FileAttribute.STANDARD_NAME + "," +
                            FileAttribute.STANDARD_TYPE + "," +
                            FileAttribute.STANDARD_IS_HIDDEN + "," +
                            FileAttribute.STANDARD_CONTENT_TYPE;
    private delegate void FolderHandler (File folder);

    private bool done;

    public string base_path { construct set; get; }

    /**
     * Create a recursive module loader for a given path.
     *
     * Either call load_modules() or load_modules_sync() to start descending
     * into the folder hierarchy and load the modules.
     *
     * @param path base path of the loader.
     */
    public RecursiveModuleLoader (string path) {
        Object (base_path : path);
    }

    public override void constructed () {
        base.constructed ();
        this.done = false;
    }

    // Plugin loading functions

    /**
     * Walk asynchronously through the tree and load modules.
     */
    public void load_modules () {
        assert (Module.supported());

        var folder = File.new_for_path (this.base_path);
        if (folder == null || !this.is_folder (folder)) {
            warning (_("Failed to open plugins folder: '%s'"),
                     this.base_path);

            return;
        }

        this.load_modules_from_folder.begin (folder);
    }

    /**
     * Walk synchronously through the tree and load modules.
     */
    public void load_modules_sync (Cancellable? cancellable = null) {
        debug ("Searching for modules in folder '%s'",
               this.base_path);
        var queue = new Queue<File> ();

        queue.push_head (File.new_for_path (this.base_path));
        while (!queue.is_empty ()) {
            if (cancellable != null && cancellable.is_cancelled ()) {
                break;
            }

            var folder = queue.pop_head ();
            try {
                var enumerator = folder.enumerate_children
                                        (LOADER_ATTRIBUTES,
                                         FileQueryInfoFlags.NONE,
                                         cancellable);
                var info = enumerator.next_file (cancellable);
                while (info != null) {
                    this.handle_file_info (folder, info, (subfolder) => {
                        queue.push_head (subfolder);
                    });
                    info = enumerator.next_file (cancellable);
                }
            } catch (Error error) {
                debug ("Failed to enumerate folder %s: %s",
                       folder.get_path (),
                       error.message);
            }
        }
    }

    /**
     * Load module from file.
     * @param file File to load the module from
     * @return The implementation should return true if the class should
     * continue to search for modules, false otherwise.
     */
    protected abstract bool load_module_from_file (File file);

    protected abstract bool load_module_from_info (PluginInformation info);

    /**
     * Process children of a folder.
     *
     * Recurse into folders or call load_module_from_file() if it looks
     * like a shared library.
     *
     * @param folder the folder
     */
    private async void load_modules_from_folder (File folder) {
        debug ("Searching for modules in folder '%s'.", folder.get_path ());

        GLib.List<FileInfo> infos;
        FileEnumerator enumerator;

        try {
            enumerator = yield folder.enumerate_children_async
                                        (LOADER_ATTRIBUTES,
                                         FileQueryInfoFlags.NONE,
                                         Priority.DEFAULT,
                                         null);

            infos = yield enumerator.next_files_async (int.MAX,
                                                       Priority.DEFAULT,
                                                       null);
        } catch (Error error) {
            critical (_("Error listing contents of folder '%s': %s"),
                      folder.get_path (),
                      error.message);

            return;
        }

        foreach (var info in infos) {
            if (this.done) {
                break;
            }

            this.handle_file_info (folder, info, (subfolder) => {
                this.load_modules_from_folder.begin (subfolder);
            });
        }

        debug ("Finished searching for modules in folder '%s'",
               folder.get_path ());
    }

    /**
     * Process a file info.
     *
     * Utility method used by sync and async tree walk.
     * @param folder parent folder
     * @param info the FileInfo of the file to process
     * @param handler a call-back if the FileInfo represents a folder.
     */
    private void handle_file_info (File          folder,
                                   FileInfo      info,
                                   FolderHandler handler) {
            var file = folder.get_child (info.get_name ());

            if (this.is_folder_eligible (info)) {
                handler (file);
            } else if (info.get_name ().has_suffix (".plugin")) {
                try {
                    var plugin_info = PluginInformation.new_from_file (file);

                    if (!this.load_module_from_info (plugin_info)) {
                        this.done = true;
                    }
                } catch (Error error) {
                    warning (_("Could not load plugin: %s"),
                             error.message);
                }
            }

    }

    private bool is_folder_eligible (FileInfo file_info) {
        return file_info.get_file_type () == FileType.DIRECTORY &&
               (file_info.get_name () == ".libs" ||
                !file_info.get_is_hidden ());
    }

    /**
     * Check if a File is a folder.
     *
     * @param file the File to check
     * @return true, if file is folder, false otherwise.
     */
    private bool is_folder (File file) {
        try {
            var file_info = file.query_info (FileAttribute.STANDARD_TYPE + "," +
                                             FileAttribute.STANDARD_IS_HIDDEN,
                                             FileQueryInfoFlags.NONE,
                                             null);

            return this.is_folder_eligible (file_info);
        } catch (Error error) {
            critical (_("Failed to query content type for '%s'"),
                      file.get_path ());

            return false;
        }
    }
}