1 /*
2  * Copyright 2004, 2006 Martin Fuchs
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 
20  //
21  // Explorer and Desktop clone
22  //
23  // favorites.cpp
24  //
25  // Martin Fuchs, 04.04.2004
26  //
27 
28 
29 #include <precomp.h>
30 
31 #include "startmenu.h"
32 
33 
34 String DecodeURLString(const char* s)
35 {
36 	TCHAR buffer[BUFFER_LEN];
37 	LPTSTR o = buffer;
38 
39 	for(const char* p=s; *p; ++p)
40 		if (*p == '%') {
41 			if (!strncmp(p+1, "20", 2)) {
42 				*o++ = ' ';
43 				p += 2;
44 			} else
45 				*o++ = *p;
46 		} else
47 			*o++ = *p;
48 
49 	return String(buffer, o-buffer);
50 }
51 
52 
53  /// read .URL file
54 bool Bookmark::read_url(LPCTSTR path)
55 {
56 	char line[BUFFER_LEN];
57 
58 	tifstream in(path);
59 
60 	while(in.good()) {
61 		in.getline(line, BUFFER_LEN);
62 
63 		const char* p = line;
64 		while(isspace(*p))
65 			++p;
66 
67 		const char* keyword = p;
68 		const char* eq = strchr(p, '=');
69 
70 		if (eq) {
71 			const char* cont = eq + 1;
72 			while(isspace(*cont))
73 				++cont;
74 
75 			if (!_strnicmp(keyword, "URL", 3))
76 				_url = DecodeURLString(cont);
77 			else if (!_strnicmp(keyword, "IconFile", 8))
78 				_icon_path = DecodeURLString(cont);
79 		}
80 	}
81 
82 	return true;
83 }
84 
85  /// convert XBEL bookmark node
86 bool Bookmark::read(const_XMLPos& pos)
87 {
88 	_url = pos.get("href").c_str();
89 
90 	if (pos.go_down("title")) {
91 		_name = pos->get_content();
92 		pos.back();
93 	}
94 
95 	if (pos.go_down("desc")) {
96 		_description = pos->get_content();
97 		pos.back();
98 	}
99 
100 	if (pos.go_down("info")) {
101 		const_XMLChildrenFilter metadata(pos, "metadata");
102 
103 		for(const_XMLChildrenFilter::const_iterator it=metadata.begin(); it!=metadata.end(); ++it) {
104 			const XMLNode& node = **it;
105 			const_XMLPos sub_pos(&node);
106 
107 			if (node.get("owner") == "ros-explorer") {
108 				if (sub_pos.go_down("icon")) {
109 					_icon_path = sub_pos.get("path").c_str();
110 					_icon_idx = XS_toi(sub_pos.get("index"));
111 
112 					sub_pos.back();	// </icon>
113 				}
114 			}
115 		}
116 
117 		pos.back();	// </metadata>
118 		pos.back();	// </info>
119 	}
120 
121 	return !_url.empty();	// _url is mandatory.
122 }
123 
124  /// write XBEL bookmark node
125 void Bookmark::write(XMLPos& pos) const
126 {
127 	pos.create("bookmark");
128 
129 	pos["href"] = _url.c_str();
130 
131 	if (!_name.empty()) {
132 		pos.create("title");
133 		pos->set_content(_name);
134 		pos.back();
135 	}
136 
137 	if (!_description.empty()) {
138 		pos.create("desc");
139 		pos->set_content(_description);
140 		pos.back();
141 	}
142 
143 	if (!_icon_path.empty()) {
144 		pos.create("info");
145 		pos.create("metadata");
146 		pos["owner"] = "ros-explorer";
147 		pos.create("icon");
148 		pos["path"] = _icon_path.c_str();
149 		pos["index"].printf(XS_TEXT("%d"), _icon_idx);
150 		pos.back();	// </icon>
151 		pos.back();	// </metadata>
152 		pos.back();	// </info>
153 	}
154 
155 	pos.back();
156 }
157 
158 
159  /// read bookmark folder from XBEL formated XML tree
160 void BookmarkFolder::read(const_XMLPos& pos)
161 {
162 	if (pos.go_down("title")) {
163 		_name = pos->get_content();
164 		pos.back();
165 	}
166 
167 	if (pos.go_down("desc")) {
168 		_description = pos->get_content();
169 		pos.back();
170 	}
171 
172 	_bookmarks.read(pos);
173 }
174 
175  /// write bookmark folder content from XBEL formated XML tree
176 void BookmarkFolder::write(XMLPos& pos) const
177 {
178 	pos.create("folder");
179 
180 	if (!_name.empty()) {
181 		pos.create("title");
182 		pos->set_content(_name);
183 		pos.back();
184 	}
185 
186 	if (!_description.empty()) {
187 		pos.create("desc");
188 		pos->set_content(_description);
189 		pos.back();
190 	}
191 
192 	_bookmarks.write(pos);
193 }
194 
195 
196 BookmarkNode::BookmarkNode()
197  :	_type(BMNT_NONE)
198 {
199 	_pbookmark = NULL;
200 }
201 
202 BookmarkNode::BookmarkNode(const Bookmark& bm)
203  :	_type(BMNT_BOOKMARK)
204 {
205 	_pbookmark = new Bookmark(bm);
206 }
207 
208 BookmarkNode::BookmarkNode(const BookmarkFolder& bmf)
209  :	_type(BMNT_FOLDER)
210 {
211 	_pfolder = new BookmarkFolder(bmf);
212 }
213 
214 BookmarkNode::BookmarkNode(const BookmarkNode& other)
215  :	_type(other._type)
216 {
217 	if (other._type == BMNT_BOOKMARK)
218 		_pbookmark = new Bookmark(*other._pbookmark);
219 	else if (other._type == BMNT_FOLDER)
220 		_pfolder = new BookmarkFolder(*other._pfolder);
221 	else
222 		_pbookmark = NULL;
223 }
224 
225 BookmarkNode::~BookmarkNode()
226 {
227 	if (_type == BMNT_BOOKMARK)
228 		delete _pbookmark;
229 	else if (_type == BMNT_FOLDER)
230 		delete _pfolder;
231 }
232 
233 BookmarkNode& BookmarkNode::operator=(const Bookmark& bm)
234 {
235 	clear();
236 
237 	_pbookmark = new Bookmark(bm);
238 
239 	return *this;
240 }
241 
242 BookmarkNode& BookmarkNode::operator=(const BookmarkFolder& bmf)
243 {
244 	clear();
245 
246 	_pfolder = new BookmarkFolder(bmf);
247 
248 	return *this;
249 }
250 
251 BookmarkNode& BookmarkNode::operator=(const BookmarkNode& other)
252 {
253 	clear();
254 
255 	_type = other._type;
256 
257 	if (other._type == BMNT_BOOKMARK)
258 		_pbookmark = new Bookmark(*other._pbookmark);
259 	else if (other._type == BMNT_FOLDER)
260 		_pfolder = new BookmarkFolder(*other._pfolder);
261 
262 	return *this;
263 }
264 
265 void BookmarkNode::clear()
266 {
267 	if (_type == BMNT_BOOKMARK) {
268 		delete _pbookmark;
269 		_pbookmark = NULL;
270 	}
271 	else if (_type == BMNT_FOLDER) {
272 		delete _pfolder;
273 		_pfolder = NULL;
274 	}
275 
276 	_type = BMNT_NONE;
277 }
278 
279 
280  /// read bookmark list from XBEL formated XML tree
281 void BookmarkList::read(const_XMLPos& pos)
282 {
283 	const XMLNode::Children& children = pos->get_children();
284 
285 	for(XMLNode::Children::const_iterator it=children.begin(); it!=children.end(); ++it) {
286 		const XMLNode& node = **it;
287 		const_XMLPos sub_pos(&node);
288 
289 		if (node == "folder") {
290 			BookmarkFolder folder;
291 
292 			folder.read(sub_pos);
293 
294 			push_back(folder);
295 		} else if (node == "bookmark") {
296 			Bookmark bookmark;
297 
298 			if (bookmark.read(sub_pos))
299 				push_back(bookmark);
300 		}
301 	}
302 }
303 
304  /// write bookmark list into XBEL formated XML tree
305 void BookmarkList::write(XMLPos& pos) const
306 {
307 	for(const_iterator it=begin(); it!=end(); ++it) {
308 		const BookmarkNode& node = *it;
309 
310 		if (node._type == BookmarkNode::BMNT_FOLDER) {
311 			const BookmarkFolder& folder = *node._pfolder;
312 
313 			folder.write(pos);
314 
315 			pos.back();
316 		} else if (node._type == BookmarkNode::BMNT_BOOKMARK) {
317 			const Bookmark& bookmark = *node._pbookmark;
318 
319 			if (!bookmark._url.empty())
320 				bookmark.write(pos);
321 		}
322 	}
323 }
324 
325 
326  /// fill treeview control with bookmark tree content
327 void BookmarkList::fill_tree(HWND hwnd, HTREEITEM parent, HIMAGELIST himagelist, HDC hdc_wnd) const
328 {
329 	TV_INSERTSTRUCT tvi;
330 
331 	tvi.hParent = parent;
332 	tvi.hInsertAfter = TVI_LAST;
333 
334 	TV_ITEM& tv = tvi.item;
335 	tv.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM;
336 
337 	for(const_iterator it=begin(); it!=end(); ++it) {
338 		const BookmarkNode& node = *it;
339 
340 		tv.lParam = (LPARAM)&node;
341 
342 		if (node._type == BookmarkNode::BMNT_FOLDER) {
343 			const BookmarkFolder& folder = *node._pfolder;
344 
345 			tv.pszText = (LPTSTR)folder._name.c_str();
346 			tv.iImage = 3;			// folder
347 			tv.iSelectedImage = 4;	// open folder
348 			HTREEITEM hitem = TreeView_InsertItem(hwnd, &tvi);
349 
350 			folder._bookmarks.fill_tree(hwnd, hitem, himagelist, hdc_wnd);
351 		} else if (node._type == BookmarkNode::BMNT_BOOKMARK) {
352 			const Bookmark& bookmark = *node._pbookmark;
353 
354 			tv.pszText = (LPTSTR)bookmark._name.c_str();
355 			tv.iImage = 1;			// bookmark
356 			tv.iSelectedImage = 2;	// selected bookmark
357 
358 			if (!bookmark._icon_path.empty()) {
359 				const Icon& icon = g_Globals._icon_cache.extract(bookmark._icon_path, bookmark._icon_idx);
360 
361 				if ((ICON_ID)icon != ICID_NONE)
362 					tv.iImage = tv.iSelectedImage = icon.add_to_imagelist(himagelist, hdc_wnd);
363 			}
364 
365 			(void)TreeView_InsertItem(hwnd, &tvi);
366 		}
367 	}
368 }
369 
370 
371  /// import Internet Explorer bookmarks from Favorites folder into bookmark list
372 void BookmarkList::import_IE_favorites(ShellDirectory& dir, HWND hwnd)
373 {
374 	TCHAR path[MAX_PATH], ext[_MAX_EXT];
375 
376 	dir.smart_scan(SORT_NAME, SCAN_DONT_EXTRACT_ICONS);
377 
378 	for(Entry*entry=dir._down; entry; entry=entry->_next) {
379 		if (entry->_shell_attribs & SFGAO_HIDDEN)	// ignore files like "desktop.ini"
380 			continue;
381 
382 		String name;
383 
384 		if (entry->_etype == ET_SHELL)
385 			name = dir._folder.get_name(static_cast<ShellEntry*>(entry)->_pidl);
386 		else
387 			name = entry->_display_name;
388 
389 		if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
390 			BookmarkFolder new_folder;
391 
392 			new_folder._name = DecodeXMLString(name);
393 
394 			if (entry->_etype == ET_SHELL) {
395 				ShellDirectory new_dir(dir._folder, static_cast<ShellEntry*>(entry)->_pidl, hwnd);
396 				new_folder._bookmarks.import_IE_favorites(new_dir, hwnd);
397 			} else {
398 				entry->get_path(path, COUNTOF(path));
399 				ShellDirectory new_dir(GetDesktopFolder(), path, hwnd);
400 				new_folder._bookmarks.import_IE_favorites(new_dir, hwnd);
401 			}
402 
403 			push_back(new_folder);
404 		} else {
405 			Bookmark bookmark;
406 
407 			bookmark._name = DecodeXMLString(name);
408 
409 			entry->get_path(path, COUNTOF(path));
410 			_tsplitpath_s(path, NULL, 0, NULL, 0, NULL, 0, ext, COUNTOF(ext));
411 
412 			if (!_tcsicmp(ext, TEXT(".url"))) {
413 				bookmark.read_url(path);
414 				push_back(bookmark);
415 			} else {
416 				///@todo read shell links
417 				//assert(0);
418 			}
419 		}
420 	}
421 }
422 
423 
424  /// read XBEL bookmark file
425 bool Favorites::read(LPCTSTR path)
426 {
427 	XMLDoc xbel;
428 
429 	if (!xbel.read_file(path)) {
430 		if (!xbel._errors.empty())
431 			MessageBox(g_Globals._hwndDesktop, xbel._errors.str(),
432 						TEXT("ROS Explorer - reading bookmark file"), MB_OK);
433 	}
434 
435 	const_XMLPos pos(&xbel);
436 
437 	if (!pos.go_down("xbel"))
438 		return false;
439 
440 	super::read(pos);
441 
442 	pos.back();
443 
444 	return true;
445 }
446 
447  /// write XBEL bookmark file
448 void Favorites::write(LPCTSTR path) const
449 {
450 	XMLDoc xbel;
451 
452 	XMLPos pos(&xbel);
453 	pos.create("xbel");
454 	super::write(pos);
455 	pos.back();
456 
457 	xbel._format._doctype._name = "xbel";
458 	xbel._format._doctype._public = "+//IDN python.org//DTD XML Bookmark Exchange Language 1.0//EN//XML";
459 	xbel._format._doctype._system = "http://www.python.org/topics/xml/dtds/xbel-1.0.dtd";
460 
461 	xbel.write_file(path);
462 }
463 
464  /// import Internet Explorer bookmarks from Favorites folder
465 bool Favorites::import_IE_favorites(HWND hwnd)
466 {
467 	WaitCursor wait;
468 
469 	StartMenuShellDirs dirs;
470 
471 	try {
472 		dirs.push_back(ShellDirectory(GetDesktopFolder(), SpecialFolderPath(CSIDL_COMMON_FAVORITES, hwnd), hwnd));
473 		dirs.push_back(ShellDirectory(GetDesktopFolder(), SpecialFolderPath(CSIDL_FAVORITES, hwnd), hwnd));
474 	} catch(COMException&) {
475 	}
476 
477 	for(StartMenuShellDirs::iterator it=dirs.begin(); it!=dirs.end(); ++it) {
478 		StartMenuDirectory& smd = *it;
479 		ShellDirectory& dir = smd._dir;
480 
481 		try {
482 			super::import_IE_favorites(dir, hwnd);
483 		} catch(COMException&) {
484 		}
485 	}
486 
487 	return true;
488 }
489