Compare commits

...
Author SHA1 Message Date
James Turner 7fbdd9ad2f MSVC lacks stdint.h / inttypes.h
- we can't use simgear/misc/stdint.hxx here, since it's a C++
header and md5.c is compiled as plain C (no bool type available).
2014-02-10 18:03:38 +00:00
James Turner 24539968fd Replace md5 from RSA with OpenBSD version
- no more BSD-with-advertising-license
- files from http://sources.debian.net/src/dpkg/1.17.6/lib/compat
(with SG_ prefixes to avoid collision issues)
2014-02-10 18:03:13 +00:00
James Turner f13b356531 Race-condition fix in get_effect.
Adapted from a fix by Jeff Biggs. (Shows up easier when using osgEarth,
and multiple osgDB pager threads)
2014-02-10 18:02:58 +00:00
Thomas Geymayer 477594d7bc Canvas: Fix bounding box calculation for paths with subpaths. 2014-02-08 00:25:03 +01:00
Thomas Geymayer e955a18126 Canvas::Element: expose combined transformation matrix 2014-02-08 00:24:55 +01:00
James Turner adec1c4507 Add desktop() accessor to SGPath
(Windows-only for the moment)
2014-02-01 09:23:21 +00:00
James Turner 172b8a032c Crash-fix: make mat-lib reference counted.
Make SGReaderWriterOptions own the material lib it's passing to
loader code, so it cannot be freed unexpectedly.
2014-01-23 09:18:39 +00:00
Torsten Dreyer d6c8008978 No double-zero for the version number :-/ 2014-01-17 21:49:13 +01:00
James Turner 488ca0fcc5 Remove a noisy log message from the Terrasync code. 2014-01-17 17:46:46 +00:00
James Turner dfd15cadab Bug-fix - short-circuit path validation.
When an SGPath is empty, don't bother stat()-ing. This avoids
an uninitialized-memory read inside the C-runtime on Windows.
2014-01-17 17:26:29 +00:00
James Turner 1613257bdc Ensure type tag of contextId binding is set.
(Otherwise defaults to DOUBLE).
2014-01-17 16:27:54 +00:00
Torsten Dreyer 1061580118 Version 3.0.0 2014-01-16 21:47:13 +01:00
James Turner 7184828a58 Support a 'clear' operation on SGBinding.
Needed to bug-fix a problem with the menubar on Mac - post-3.0 I will
change the behaviour of SGBinding's destructor to avoid the property
copies in other use-cases.
2014-01-15 21:56:26 +00:00
Thomas Geymayer 0428adff03 CanvasMap: Expose property screen-range.
Only allowing a hardcoded screen range of 200 units
is not very flexible...
2014-01-09 19:36:38 +01:00
James Turner 04246f0a63 HTTP: tweak response parsing.
Signal a parse failure via an exception, instead of via a status
code, since this is hard to disambiguate from a valid server-side
status code.
2014-01-07 16:22:10 +00:00
James Turner 61df58d651 Terrasync: update fixes.
- Fix a nasty issue when updating existing repositories, which could
cause repository corruption and hence repeated re-syncs.
2014-01-07 14:24:01 +00:00
James Turner 1bd9a440e8 HTTP: tweak for malformed header handling. 2014-01-07 14:24:01 +00:00
Thomas Geymayer d82c8eb945 Socket: do not crash on writing to broken pipe.
Writing to a closed socket on all Unix/Linux systems raises
a SIGPIPE signal which immediately closes the program. Disabling
raising of SIGPIPE allows proper error handling by checking the
return values of send/sendto and getting the EPIPE in errno.
2014-01-06 12:17:59 +01:00
James Turner becbad7ea4 Crash fix: use cache to find materials.
Add a thread-safe cache of the currently valid materials, to avoid
the osgDB thread making repeated SGCondition evaluations in conflict
with the main thread.

(Requires an accompanying FG change)
2014-01-06 08:26:48 +00:00
Thomas Geymayer 2335796a28 Fix track-to animation basic tracking without slave object 2014-01-05 19:38:03 +01:00
James Turner c6330b64f6 Bugs 1207, 1301
Only apply the fix for time-zone offsetting on Windows, since Unix
is handling mktime differently. (Arguably we should also apply a
conversion for Unix systems, but the previous logic worked)
2014-01-03 19:51:58 +00:00
James Turner df57a23512 #1207: Add helper to get Unix time in GMT
On Windows, time() is returning a value which if offset by the
selected time-zone. Provide a variant which is always in GMT.
2014-01-01 21:03:24 +00:00
Thomas Geymayer aea71cf0af HTTPFileRequest: only save file if status == 200 2013-12-30 01:31:47 +01:00
James Turner fb54386f8d Bugfix: remove SGSky minimum distance check.
Without this, various sky rendering modes exhibit artefacts in low-
visibility conditions. The cost of always rendering the dome is 
negligible. (And Rembrandt already had to work this way)
2013-12-19 17:20:06 +00:00
Rebecca Palmer 4d931b6109 Fix test failure on char-unsigned-by-default architectures (eg. ARM)
(from http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=722115 )
2013-12-19 15:02:07 +00:00
Rebecca Palmer 407c7a10f5 Fix buffer overflow CVE-2012-2091 (thanks to Saikrishna Arcot)
https://bugs.launchpad.net/ubuntu/+source/simgear/+bug/1077624
(discussed in comments 65-78)
(This is already fixed in 2.12 but appears to have been forgotten in 2.99;
the other vulnerabilities described there are already fixed.)
2013-12-19 15:02:07 +00:00
James Turner 16fa4d1d17 Security: fix 0777 directory permissions to 0755. 2013-12-19 14:56:23 +00:00
James Turner 1ecccd43d4 Fix a typo breaking Mac build. 2013-12-15 20:55:02 +00:00
James Turner 2edbb1cf8b Reset: clear some property references. 2013-12-15 20:48:30 +00:00
Thomas Geymayer 8dccbe8029 SGPath: some tweaks/fixes/typo on permission checker. 2013-12-12 21:27:27 +01:00
Thomas Geymayer 031c833e4d SGPath: add support for custom PermissionChecker
Allow to add permission checks (read/write-access) for
eg. limiting access based on IORules.
2013-12-12 21:04:26 +01:00
James Turner 890cc46825 Reset: alternate property copying strategy.
Search the tree for leaves / sub-trees with a particular attribute set.
This permits both sparse selection as well as selection by sub-tree.

(This is needed for PRESERVE attribute, and once new reset is switched
on, will enable 'normal' copyProperties to revert to ignoring
attributes completely)
2013-12-10 17:37:37 +00:00
James Turner 6b0f7e8512 Quiet a message. 2013-12-09 23:28:52 +00:00
Thomas Geymayer 7a220921f0 Canvas: one global SystemAdapter is enough.
This also fixes elements requiring a SystemAdapter without
being assigned to a Canvas (eg. because they are desktop
elements).
2013-12-09 23:07:17 +01:00
Thomas Geymayer 7ef9acda5d Add ListDiff helper (for fast array differences). 2013-12-08 18:48:57 +01:00
Thomas Geymayer 2a6d62e6f7 Fix some unused/uninitialized variables. 2013-12-07 13:43:17 +01:00
Thomas Geymayer 42b50bd7f7 SGPropertyNode: add removeAllChildren() method 2013-12-07 13:42:15 +01:00
Thomas Geymayer 2c3f44ae11 Canvas: ensure all canvasses are destroyed
- For shutdown/reset make sure all properties describing a canvas
   are removed and also not restored after the reset.
2013-12-07 13:40:22 +01:00
Stuart Buchanan b7662f16e5 Work around apparent OSG 3.2.0 normal binding bug. 2013-12-06 22:12:52 +00:00
Stuart Buchanan 5bc535a8d2 PagedLOD for random trees, buildings and objects. 2013-12-06 20:20:46 +00:00
James Turner 3ec9c7ae6e Canvas: clear elements in shutdown()
- without this change, elements aren't un-ref-ed and hence destroyed
until the actual dtor runs, which is too late (other systems may be
destroyed already)
2013-12-06 18:14:48 +00:00
James Turner e326d0c756 Work around a clang/libc++ issue. 2013-12-06 18:03:35 +00:00
Thomas Geymayer 7c5434a99f cppbind: don't leak nasal references for Ghost methods. 2013-12-04 11:15:11 +01:00
James Turner 26d9256ee9 Reset: hook to clear the effects cache. 2013-12-04 09:12:51 +00:00
James Turner 053e761d8f Reset: don't leak EffectPropertyListener.
Track and free listeners from effect expressions.
2013-12-03 21:36:18 +00:00
Clément de l'Hamaide 5861b52cff Implement UTF-8 to Latin1 converter 2013-12-01 18:48:05 +01:00
Thomas Geymayer 66193d7283 cppbind: Fix detecting derived Ghosts 2013-12-01 12:35:02 +01:00
Thomas Geymayer dfb07f359e cppbind: make ghost_type static to keep same pointer on reset.
Using a static naGhostType ensures that always the same
address is used for the same ghost and prevents ghost_types
from getting invalid (eg. on reset).
2013-12-01 01:18:13 +01:00
Thomas Geymayer 6fe1433497 Canvas: allow removing placement factories. 2013-11-24 15:39:03 +01:00
bcoconni 01ace109ba Fixed a crash: the singleton needs to be instantiated the first time SGCommandMgr::instance() is called 2013-11-24 14:50:11 +01:00
bcoconni 8e01acaa12 Added more explicit error messages to the property lists parser.
More specifically, each time an error or a warning is issued the location of the corresponding XML tag (file name an line number) is reported.
2013-11-24 12:20:26 +01:00
James Turner 7bbec8dbcb Reset: allow re-init of Nasal Ghosts.
After discussion with Thomas, reset needs the scenario this assert
was designed to guard against, so removing it for now.
2013-11-23 20:06:47 +00:00
James Turner 10375086ed Allow Command-manager singleton to be deleted.
(Shutdown can delete commands in an orderly way)
2013-11-23 20:00:23 +00:00
James Turner dd367daac9 Fix another SVN crash on failure. 2013-11-19 21:33:50 +00:00
Martin Spott 27063d0c32 Repository name preferred 2013-11-16 10:44:17 -08:00
James Turner 80ff0282c3 Give terrasync a custom HTTP UA. 2013-11-16 09:45:40 +00:00
James Turner e973e406a3 Fix some SVN errors/crashes. 2013-11-15 11:47:36 +00:00
bcoconni c64776029e Removed the 'using::std' instructions from the headers as per James Turner request.
Also took this opportunity to add a check in XMLVisitor::savePosition() against a null pointer.
2013-11-14 22:07:29 +00:00
bcoconni 5d7d0a922d Added the ability to access the file name and position (line, column) from which the XML visitor is getting its data 2013-11-14 22:07:29 +00:00
Christian Schmitt c2c96c4219 apt_signs: lower the log level for apt.dat syntax errors. Nobody seems to care... 2013-11-14 18:41:20 +01:00
James Turner 3e9ed08c53 Reset: use ref-counting to own subsystems.
Change the subsystem-group and manager code to use shared-ptr references
to subsystems, instead of holding a raw pointer. Hence the manager becomes
the owning ref to most subsystems.
2013-11-12 23:12:45 +00:00
James Turner b7df9026c0 Reset: Terrasync root can change, can be shutdown. 2013-11-12 23:04:13 +00:00
James Turner 6527c6357b Reset: clean event-manager shutdown 2013-11-12 22:23:19 +00:00
James Turner fad333256c Reset: DeletionManager can be uninstalled 2013-11-12 22:23:14 +00:00
James Turner 8753811fe1 Remove exceptions on missing texture names 2013-11-12 22:23:09 +00:00
James Turner 237752ff62 String case conversion, UTF-8 conversion. 2013-11-12 22:17:12 +00:00
Thomas Geymayer 743244a69c CanvasElement: ensure parent pointer can not become invalid. 2013-11-10 17:12:50 +01:00
Thomas Geymayer 9f89737986 track-to animation: fix "short" and "long" tracking. 2013-11-10 11:14:50 +01:00
Thomas Geymayer 508a5c5f66 track-to animation: 2dof rotation for slave object.
Slave objects can now rotate around two axis to always track the
target - even when it is outside the plane perpendicular to the
lock-axis of the root object. This allows for example animating
levers with non-parallel axis of rotation connected with a rod.
2013-11-08 00:51:18 +01:00
James Turner d84394efa0 Tweak HTTP code to always sleep.
Check explicitly for the 'no channels' case and
sleep instead, to avoid busy-waiting. (This is a work-
around, the underlying issue still be be traced)
2013-11-06 15:11:46 -08:00
James Turner dd613d48bc Reset: pin ctor and dtor of event manager. 2013-11-05 05:21:57 +00:00
James Turner 2e8e500d4f Reset: commands can be removed 2013-11-05 05:21:52 +00:00
James Turner 48f866dcc6 TerraSync: avoid assert on missing file 2013-11-05 05:17:57 +00:00
Thomas Geymayer ec4d3e4f9b Make new gcc happy (include cmath) 2013-11-04 10:48:26 +01:00
Thomas Geymayer 38ddfab1e0 Canvas: improved clipping and new property clip-frame.
- Update clipping rect if canvas view or texture size
   changes.
 - Add new property "clip-frame" to specify coordinate
   frame for "clip" coordinates. Coordinates can be
   global, relative to the parent or relative to the
   element itself.
2013-11-03 20:12:45 +01:00
Thomas Geymayer 353b7e4438 SGPropertyNode: extract enum value with getValue 2013-11-03 20:11:06 +01:00
Thomas Geymayer 7076c9a0ff HTTP: finish core request before calling any callback. 2013-10-28 20:34:11 +01:00
James Turner 2f42a8714c Terrasync: don't busy wait when no requests are running. 2013-10-28 14:51:52 +00:00
Thomas Geymayer fdf6fc32ff HTTPFileRequest: create directory if it does not exist 2013-10-28 12:38:39 +01:00
Thomas Geymayer f2188b33c6 HTTP: Rename urlretrieve/urlload to save/load. 2013-10-27 23:38:46 +01:00
Thomas Geymayer f93fead8f2 io: refactor and improve HTTP modules.
- refactor code used multiple times spread over sg/fg into
   one single location.
 - allow aborting requests.
 - Provide two common request types:
  * FileRequest: Save response into file
  * MemoryRequest: Keep resonse in memory (std::string)
 - extend HTTP::Client interface:
  * urlretrieve: Save url to file (shortcut for making a
                 FileRequest)
  * urlload: Get respons into memory (shortcut for making
             a MemoryRequest)
2013-10-27 19:05:49 +01:00
Thomas Geymayer 050f3791cc New class simgear::Map extending std::map interface. 2013-10-27 19:05:49 +01:00
James Turner 94326fd964 Windows logging changes
- can log to the debugger (MSVC output window) via OutputDebugStr
- logging to the console is optional, triggered by new
  requestConsole hook()
2013-10-26 21:04:16 +01:00
James Turner f26f3b606d Fix handling of SGPath::rename
Windows handling of ::rename differs from POSIX when the new name
exists. Check for this case on Windows and remove the file manually.
(This allows a work-around in SVNDirectory to be removed)
2013-10-26 21:03:00 +01:00
Thomas Geymayer 7cbfa76be4 cppbind: automatic conversion of SGReferenced derived pointers. 2013-10-26 01:04:16 +02:00
James Turner d896e71ae9 Add total bytes downloaded tracking. 2013-10-24 23:35:44 +01:00
Thomas Geymayer ca79d99ec4 canvas::Element: add getter for parent element. 2013-10-23 17:11:03 +02:00
James Turner 95f77ef81d Ooops, fix INSTALL rule.
We need the rule, just not the export, for now.
2013-10-23 14:18:39 +01:00
James Turner c4e7d26b70 Comment out Cmake export experiments for now.
This is breaking CMake 2.6, so commenting out for the moment, until
I understand how to support this portably.
2013-10-22 13:42:59 +01:00
James Turner ea49a1a07b Fix GPS activation on Windows.
Bug 1190, fix route-manager -> GPS activation on Windows
(and probably a couple of unrelated issues).
SGPropertyChangeCallback was missing a suitable copy-constructor,
leading to incorrect behaviour on compilers where the operation
is not elided. Such as MSVC.
2013-10-22 00:42:31 +01:00
James Turner 784223c67f Fix Dir warnings on Windows.
Test result of GetLastError so we don't log a warning in the
'nothing matched' case. Makes early startup logging much
more pleasant, especially when scanning aircraft dirs.
2013-10-21 23:51:19 +01:00
James Turner 77aa2c9a9d Smoother download rate from HTTP
Crude filtering (low pass) of the download rate, over a 400msec
time window.
2013-10-21 23:08:55 +01:00
James Turner 48145fb234 Fail HTTP request with missing/illegal protocols.
Instead of throwing, run the failure handler for requests with
either a missing spec or something other than http:// (eg, https
or ftp)
2013-10-20 23:20:49 +01:00
James Turner 49730cadee Placement can contain multiple nodes.
- Allow placement to have multiple children added.
- Remove legacy position setting APIs, enforce use of SGGeod
2013-10-20 20:44:42 +01:00
Thomas Geymayer 0b197501e1 Add SGPropertyNode::setValueReadOnly.
Helper to set value of relative node and mark
read only. Use eg. for exposing configuration
and build values to the property tree.
2013-10-20 12:03:41 +02:00
James Turner 8b0c246a7b Avoid a divide-by-zero on malformed BTG files.
Encountered while testing v850 airports; some airports
generate materials with no tris, and hence no indices.
This causes a divide-by-zero when computing the index stride.

Detect this, and convert the BTG reader to throw exceptions
in error conditions, and to catch this and report the
appropriate result code.
2013-10-17 20:13:50 +01:00
James Turner 9c4face1ae Fix float-int conversion bug caught by Clang 2013-10-17 20:13:49 +01:00
James Turner 952e2e6631 Fix unused-var warnings from Clang. 2013-10-17 20:13:49 +01:00
James Turner 4766910413 Terraysnc: don't block requests when inactive.
Add the same check for data files so we don't block when terrasync is
disabled.
2013-10-17 17:29:06 +01:00
James Turner 3c2f97a8d2 Re-order install(EXPORT ) lines.
Certain cmake builds/versions complain when a dependent target is
exported but dependencies are not. Export SimGearCore before
SimGearScene.
2013-10-17 17:12:02 +01:00
James Turner f75730f165 Initial work on syncing non-scenery data.
This is some initial pieces to synchronise other pieces of base
data than scenery via the sync mechanism. An additional sync slot is
added to avoid scenery or other data blocking each other.
2013-10-17 16:39:29 +01:00
James Turner b596e62a61 Remove some iostream debugging.
Accidentally committed this debug stuff, remove it.
2013-10-17 16:35:36 +01:00
James Turner 1349d48339 Export SimGear config to cmake.
Experimenting with this, should make FindSimGear module unecessary,
and avoid mis-matched SG/FG cmake settings in the future.
2013-10-17 16:10:18 +01:00
James Turner d69ea5fda6 Kill off EMBEDDED_SIMGEAR 2013-10-17 16:09:39 +01:00
Thomas Geymayer 9b68062ba7 Add helper to get osg::Node path as string. 2013-10-17 13:28:54 +02:00
Thomas Geymayer b217019723 Nasal: do not print invalid elements for error backtraces. 2013-10-16 23:33:06 +02:00
Thomas Geymayer a37a254a68 Canvas: simplify code by using new nasal function conversion. 2013-10-15 17:46:42 +02:00
Thomas Geymayer 5258699f20 cppbind: convert Nasal functions to C++ boost::function. 2013-10-15 17:46:31 +02:00
Thomas Geymayer 483bebb003 cppbind: fix bad class hierarchy for bad_nasal_cast. 2013-10-15 16:38:38 +02:00
Thomas Geymayer 8ca8052a8d Nasal: use correct function pointer in naCall (with user data) 2013-10-15 14:21:45 +02:00
Thomas Geymayer d68b1144b8 Nasal: recursive method calling.
New functions naCallMethodCtx and naCallMethod to replace
NasalSystem::callMethod from FlightGear. This has just added an
unneeded level of indirection and fits better directly into Nasal.
naSetErrorHandler can be used to register an error handler/logging
function.
2013-10-15 12:08:42 +02:00
Thomas Geymayer 42c39b6be3 Canvas: use new naGCSave/naGCRelease functions. 2013-10-15 00:49:13 +02:00
Thomas Geymayer bb82b9d168 Nasal: add naGCSave/naGCRelease for preventing objects being garbage collected.
These new functions are meant to replace the gcSave/gcRelease
methods of the NasalSystem class in FlightGear, as passing an
adapter to SimGear from FlightGear is quite a lot of useless work
just for being able to save objects.
2013-10-15 00:19:32 +02:00
Thomas Geymayer b1f865d461 cppbind: add to_nasal_helper for enums. 2013-10-14 23:03:36 +02:00
Thomas Geymayer 8e75c6be50 HTTP: check url for scheme and report instead of failing with assert. 2013-10-13 12:06:17 +02:00
Thomas Geymayer 06a5f9188d Don't let exceptions escape from commands. 2013-10-13 12:00:52 +02:00
Thomas Geymayer 370a991208 cppbind: Catch exceptions before reaching C code. 2013-10-13 11:40:27 +02:00
Thomas Geymayer 6deb77dd4d Canvas: allow also C++ callable entities as event callbacks. 2013-10-12 00:29:37 +02:00
James Turner 9e3172cb04 Another attempt to make MSVC happy!
C89 requires variable declarations upfront.
2013-10-09 12:26:29 +02:00
James Turner 426c6b9a72 Linux needs <cstring> 2013-10-09 11:58:51 +02:00
James Turner d148bdedcd Fix MSVC build, no C99 so no round().
(Doesn't use SGMisc<T>::round, since Nasal is pure C)
2013-10-09 11:52:54 +02:00
James Turner 23140e3bf7 Avoid a data copy decoding base64 data.
Use an out parameter to avoid a buffer alloc/copy/free cycle
when returning base64-decoded data.
2013-10-09 10:03:30 +02:00
James Turner 2f023803e7 Refactor HTTP content-encoding support.
Move content-encoding handler into its own file, which 
simplifies the main code. As part of this, fix a bug where we
didn't flush the ZLIB buffers on response completion.
2013-10-09 10:03:28 +02:00
James Turner d658b5fc38 Use std::string.append, avoid string copying.
Removes a hot-spot on the SVN update path.
2013-10-09 10:03:27 +02:00
James Turner 68cd84c330 Make base64 decode significantly faster.
Use a reverse lookup table instead of a call to string.find for
each input byte, much speedier.
2013-10-09 10:03:27 +02:00
James Turner 0bd82a43d3 Check for DAV status parse failures. 2013-10-09 10:03:26 +02:00
James Turner 7fdf42b699 Reset: Nasal additions to force GC, clear saved refs. 2013-10-09 09:59:50 +02:00
James Turner aeb0e9aac3 Reset: event manager can be unbound. 2013-10-09 09:59:40 +02:00
James Turner 1099a3bdf0 Reset: model placement can drop OSG nodes. 2013-10-09 09:59:33 +02:00
James Turner aa3458f69c Reset: model lib can drop static root. 2013-10-09 09:59:21 +02:00
Stanislaw Halik 0186cbb7b7 cmake: prevent Boost barfing out too many warns 2013-10-03 21:49:38 +01:00
Stanislaw Halik fa36e94c4b fix static build with recent GNU binutils
Signed-off-by: Stanislaw Halik <sthalik@misaki.pl>
2013-10-03 21:49:38 +01:00
James Turner ad83e70cf5 Extend built-in Nasal math.
- rename mod() to fmod() to prevent collisions with the
version in math.nas (which has different handling of negatives)
- implement pow, tan, acos and asin natively
- add round(x, [p]), which rounds away from 0.0, and takes an
option precision. I.e you can round(479, 50) and get '500'; useful
in many digital cockpit displays.
2013-10-03 17:40:17 +01:00
James Turner 4a0377c0a1 Rename WaitingSyncItem -> SyncItem.
More accurate name (we have these for items which are not waiting),
and also shorter.
2013-09-30 21:59:18 +01:00
James Turner 83a3241830 Fix a signed/unsigned compare warning 2013-09-30 16:44:04 +01:00
James Turner f299b351e3 Expose active tiles via API, sync by SGBucket.
Provide an API to query which tiles are currently being synced. This
allows the higher levels (TileManager in FlightGear) to safely wait on
TerraSync before loading tiles.

Doing this exposed some bugs in scheduling tiles to sync by integer
lat/lon, related to round-towards-zero with -ve values (western /
souther hemispheres). To resolve these, and also reduce spurious
syncs, switch to an explicit API for requesting tiles by SGBucket.
This keeps TerraSync fully aligned with the TileManager queue, which
has many benefits for both high and low visibility situations.
2013-09-30 16:35:45 +01:00
James Turner 3880e4ef47 Say which ocean tile we generated. 2013-09-30 12:08:10 +01:00
James Turner f205e918d9 sprintf -> snprintf in bucket code. 2013-09-30 12:07:57 +01:00
James Turner 42b0b9306b Make code quieter at log-level=DEBUG. 2013-09-28 14:45:30 +01:00
James Turner 483659c319 HTTP: adjust request-connection assignment.
Prefer existing, idle connections to creating new connections,
even when below the max-connection limit. Gives much better re-use
and pipeline-ing, and hence reduced setup time/trips.
2013-09-28 14:03:39 +01:00
James Turner add14dd27c Reposition hook for terrasync.
When re-init-ing the sim, tell TerraSync what's going on (we don't want
to actually reset it, since that would disturb in-flight downloads).
Add an explicit method so it can reset its last lon/lat, and hence do
the initial sync at the new location correctly.
2013-09-27 20:28:43 +01:00
James Turner 61516a5e97 Terrasync: Expose transfer rate as a property. 2013-09-27 18:23:51 +01:00
James Turner 17418039e1 HTTP bugfix + enhancement.
Track 'waiting for response' state explicitly, so we don't
falsely report IDLE when waiting for a response.

Expose bytes-per-second download rate as metric.
2013-09-27 18:20:03 +01:00
James Turner 25cae61211 Allow TerraSync to be inited early.
Add explicit subsystem state tracking so we can safely init()
terrasync very early during startup, and hence overlap lengthy operations
(Shared Models sync and initial NavCache build)
2013-09-26 12:11:48 +01:00
James Turner db98c7440e Parallel sync of items.
When using built-in sync code, separate items into distinct slots.
Slots process items sequentially, but each slot works in parallel (using 
a single shared HTTP engine). This allows tiles to be synced in parallel
with airports/shared models data, greatly increasing responsiveness
to get tiles synced on initial launch.
2013-09-25 16:31:10 +01:00
James Turner 3783ae2234 Rename WaitingTile to WaitingSyncItem.
We already synchronise other things besides tiles, and will soon want different
behaviour based on the type of item we're syncing (so we can synchronize some items in parallel). Rename the struct to be clearer, and add an enum encoding the type of item for future use.
2013-09-23 18:20:16 +01:00
James Turner 3e8732b230 Libsvn / APR dependency is removed.
Remove any use of libsubversion / API in Simgear, and always
compile the built-in SVN client code.
2013-09-23 18:05:37 +01:00
James Turner b39bca9458 Fix no-svn build (Win64 only at present) 2013-09-20 09:18:43 +01:00
James Turner d263334030 Attempting to fix import/export linkage on Windows.
Hopefully this tells the Expat headers not to do any clever
declspec(import) or export stuff, which we don't want since we only
use the symbols within SimGearCore, and don't export them.
2013-09-19 22:07:41 +01:00
James Turner b2cea62189 Even more forcing of include file names.
(It's a good thing Windows doesn't ship Expat, since apparently
every other platform does)
2013-09-19 21:58:23 +01:00
James Turner d6b886c69b Change got lost in the move/rename, not good. 2013-09-19 21:47:40 +01:00
James Turner 38fb9ea41e Intermediate static libs have issues.
Switch back to including the bundled expat sources directly in the
targets, since transitive linking of static libs is very awkward.
Makes static build happy again (let's see what else breaks)
2013-09-19 21:42:28 +01:00
James Turner a922aaa68e Move bundled Expat to new home.
Renamed bundled Expat headers to avoid any
possibility of accidental including system headers when
using bundled Expat, or vice-versa. Should help with SVN
crashes reported by Thomas.
2013-09-19 20:12:32 +01:00
James Turner 4f2e36ca46 rand(), not random(), for Windows. 2013-09-19 08:57:34 +01:00
James Turner f367627cac Fix HTTP unit test.
Also fix a signed/unsigned warning I caused.
2013-09-18 22:55:11 +01:00
James Turner 7a7fcf10ad Fix missing include for non-Mac 2013-09-18 22:13:04 +01:00
James Turner b9bd2734eb Persistent SVN update cache.
Make the already present cache of updated URLs persistent, with a
definable lifetime, currently 24 hours. This ensures terrasync-ed
resources are checked less often (max once per day) instead of each
FGFS launch, which should greatly cut down requests to the backend.
2013-09-18 21:40:35 +01:00
James Turner 9c7bd4f5d5 HTTP Client improvements
- max connections limit, and parallel connections to a single host where possible.
  (Will permit updating terrain and Models / Airports / data in parallel)
- add LGPL headers
- give HTTP::Client a private impl class, to keep header simple.
2013-09-18 12:17:38 +01:00
James Turner 362d47f91f Kill legacy PPM support in screen-dump 2013-09-18 12:17:29 +01:00
James Turner a18792c397 Printf format sanitising.
Refactored version for next, use a new helper in
simgear::strutils.
2013-09-15 13:30:12 +01:00
James Turner 0e2ddb2f16 Bah, tested on the wrong compiler, so missed a file. 2013-09-14 18:05:53 +01:00
James Turner bcfadd6657 Prefix GCC version constant.
From: Markus Wanner <markus@bluegap.ch>
2013-09-14 17:29:59 +01:00
Thomas Geymayer 021cf1a2b4 Remove temporary file and add to .gitignore 2013-09-07 14:28:46 +02:00
James Turner acf86be516 Rename MD5 code.
Linux crashes with this code seem to relate
to multiple instances of these symbols. Prefix
the SimGear versions to prove/disprove this.
2013-09-04 08:45:06 +01:00
James Turner dd93eb88f2 Ensure sglog() singleton is threadsafe.
Slightly simplistic, but definitiely correct, method
for ensuring singleton creation is threadsafe. Can
be optimised if lock contention is ever an issue.
2013-09-04 08:45:06 +01:00
James Turner e06f9e35b3 Tweak for 64-bit Linux linkage. 2013-09-04 08:45:06 +01:00
James Turner c0424aba7e SG part of fix for TerraSync models.
Ensure we search the TerraSync models tree before the FG_ROOT one,
since the TerraSync one contains newer files and updates to the base
one.
2013-08-29 22:13:43 +01:00
Thomas Geymayer b468254d73 Fix #1163 2013-08-17 17:47:23 +02:00
Tom Paoletti 15e3e92ec2 Performance optimization: empty() instead of size()>0
empty() is guaranteed to be constant complexity for both vectors and lists, while size() has linear complexity for lists.
2013-08-16 16:48:35 +01:00
James Turner ee403fd83a No need to deal with OSG versions pre 3.0 2013-07-29 22:53:31 +01:00
James Turner 05f266ac10 HTTP SVN fixes, cap max update-report depth. 2013-07-28 21:07:40 +01:00
James Turner f52c0026dd Better reporting when an HTTP request fails. 2013-07-28 21:06:17 +01:00
Thomas Geymayer a284107249 Canvas: ensure render order equals order in property tree.
Prevents also changing the render order if changing eg. the size
of a Canvas.
2013-07-26 23:31:46 +02:00
James Turner a2e25e7940 Fix for #587, crash on break/continue.
Look for use of break/continue outside of a loop context,
and report a parser error instead of crashed. Thanks to
Philosopher for the patch.
2013-07-23 21:58:38 +01:00
James Turner d45a0161cf Initialise token precedence rule.
Thanks to Philosopher for the catch.
2013-07-23 21:51:47 +01:00
James Turner c7a5d15e7a Bug 585, assignment to multiple l-values.
Disambiguate multiple-values from function invocation. Will merge
to release branch after testing.
2013-07-23 09:45:22 +01:00
James Turner 3cb2241b1d Bug 737, crash parsing default args.
Reject function evaluation explicitly when building
default arguments.
2013-07-23 09:45:22 +01:00
Thomas Geymayer b236821661 Canvas: Ensure all element types are initialized before first usage.
If setting properties on a group a check is performed if
this property exists on any possible child element, and
only if this is the case the property is stored in the
groups style.

Previously elements have been only initialized during
their first usage, leading to ignored styles if they
have been set on a parent element before instantiating
an instance of the actual element type.
2013-07-20 15:45:02 +02:00
Thomas Geymayer fc75b0bd21 Canvas::Text: add line-height property. 2013-07-19 23:38:08 +02:00
Thomas Geymayer a9fc84d568 Canvas: clip region rounding and catch negative size. 2013-07-19 23:37:04 +02:00
Thomas Geymayer b15c6d887d Canvas: don't crash with invalid clip rect 2013-07-18 15:35:44 +02:00
Thomas Geymayer c2f89978ff canvas::Element: Fix bounding box calculation of groups 2013-07-18 14:16:13 +02:00
James Turner a004153223 Bump version on development trunk. 2013-07-17 20:29:22 +01:00
Thomas Geymayer dde2bbcbe7 nasal::String: add ends_with method 2013-07-15 22:26:11 +02:00
Thomas Geymayer 7bd572f3ec Fix inverted checks... 2013-07-13 14:06:11 +02:00
Thomas Geymayer 486011dceb Fix for OSG without OSG_INIT_SINGLETON_PROXY 2013-07-13 11:37:24 +02:00
Thomas Geymayer 01c45633b7 Canvas::Group: Print warning message if using expired Group instead of crashing 2013-07-12 18:00:53 +02:00
Thomas Geymayer 5320d0ecaa Fix render order of canvas elements 2013-07-12 16:13:06 +02:00
Thomas Geymayer 757ba03918 CSSBorder fix: width/height swapped 2013-07-06 12:05:19 +02:00
Thomas Geymayer 948db69cc9 interpolate without values is used to abort interpolating -> no warning 2013-07-04 19:46:27 +02:00
Thomas Geymayer 92f363926e Replace auto_ptr to prevent possible undefined behavior 2013-07-04 16:09:04 +02:00
Thomas Geymayer d0ff144753 Canvas: Fix removing text elements.
Removing text elements failed due to prefering removing the
style property with the same name. Add a flag to each style
indicating whether it can be inherited. The text property
is only applicable to text elements and can not be inherited.
Now groups can not have a text property, avoiding the name
clash with the text element.
2013-07-04 14:11:54 +02:00
Thomas Geymayer 943efbb3ed Canvas Event System: add missing currentTarget field. 2013-07-04 00:53:49 +02:00
Thomas Geymayer 6962de4b1f Canvas: fix property inheritance.
- Do not override style of children if they have own values set.
 - Retrieve style of parent if own style value has been removed.
2013-07-03 00:05:20 +02:00
Thomas Geymayer 6f7c0c23d1 Canvas: prevent bubbling of mouseenter and mouseleave. 2013-06-30 21:27:03 +02:00
Thomas Geymayer 14eccc70da Restore compatibility with stable OSG (3.0.1) 2013-06-30 21:25:21 +02:00
Thomas Geymayer d4b48cec5d Canvas: clear mousedown/click if mouse leaves canvas. 2013-06-29 14:29:38 +02:00
Thomas Geymayer 2d23c5351f Canvas: Improve checks to mark events as handled. 2013-06-29 14:16:39 +02:00
Thomas Geymayer b5dfaf170a Canvas: Ensure events are dispatched to canvas with no element hit. 2013-06-29 14:14:12 +02:00
Thomas Geymayer c372591f36 Canvas: Ignore hidden element on event traversal. 2013-06-29 14:12:59 +02:00
Thomas Geymayer 36d1308aa6 Canvas: fix distance check for click events. 2013-06-29 14:11:19 +02:00
Thomas Geymayer b0d6ed3844 Canvas: trigger missing events on mouseup.
On mouseup send mouseout/mouseleave to old element and
mouseenter/mouseover to new element. This fixes hover
actions after dragging.
2013-06-29 11:33:44 +02:00
197 changed files with 6670 additions and 3737 deletions
+1
View File
@@ -1,4 +1,5 @@
.* .*
*~
Makefile Makefile
*.o *.o
lib*.a lib*.a
+4
View File
@@ -0,0 +1,4 @@
if (NOT SYSTEM_EXPAT)
add_subdirectory(expat)
endif()
+33
View File
@@ -0,0 +1,33 @@
configure_file (
"${PROJECT_SOURCE_DIR}/3rdparty/expat/expat_config_cmake.in"
"${PROJECT_BINARY_DIR}/3rdparty/expat/expat_config.h"
)
set(expat_sources
asciitab.h
hashtable.h
iasciitab.h
latin1tab.h
nametab.h
utf8tab.h
xmldef.h
xmlparse.h
xmlrole.h
xmltok.h
xmltok_impl.h
hashtable.c
xmlparse.c
xmlrole.c
xmltok.c
internal.h
ascii.h
sg_expat.h
sg_expat_external.h
)
foreach(s ${expat_sources})
set_property(GLOBAL
APPEND PROPERTY LOCAL_EXPAT_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/${s}")
endforeach()
View File
+1 -1
View File
@@ -15,7 +15,7 @@
#endif #endif
#include <stdlib.h> #include <stdlib.h>
#include "expat_external.h" #include "sg_expat_external.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
View File
+1 -1
View File
@@ -23,7 +23,7 @@
#endif /* ndef COMPILED_FROM_DSP */ #endif /* ndef COMPILED_FROM_DSP */
#include "ascii.h" #include "ascii.h"
#include "expat.h" #include "sg_expat.h"
#ifdef XML_UNICODE #ifdef XML_UNICODE
#define XML_ENCODE_MAX XML_UTF16_ENCODE_MAX #define XML_ENCODE_MAX XML_UTF16_ENCODE_MAX
+1 -1
View File
@@ -18,7 +18,7 @@
#endif #endif
#endif /* ndef COMPILED_FROM_DSP */ #endif /* ndef COMPILED_FROM_DSP */
#include "expat_external.h" #include "sg_expat_external.h"
#include "internal.h" #include "internal.h"
#include "xmlrole.h" #include "xmlrole.h"
#include "ascii.h" #include "ascii.h"
+1 -1
View File
@@ -18,7 +18,7 @@
#endif #endif
#endif /* ndef COMPILED_FROM_DSP */ #endif /* ndef COMPILED_FROM_DSP */
#include "expat_external.h" #include "sg_expat_external.h"
#include "internal.h" #include "internal.h"
#include "xmltok.h" #include "xmltok.h"
#include "nametab.h" #include "nametab.h"
View File
+65 -79
View File
@@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 2.6.4)
include (CheckFunctionExists) include (CheckFunctionExists)
include (CheckIncludeFile) include (CheckIncludeFile)
include (CheckCXXSourceCompiles) include (CheckCXXSourceCompiles)
include (CheckCXXCompilerFlag)
project(SimGear) project(SimGear)
@@ -10,6 +10,8 @@ project(SimGear)
file(READ version versionFile) file(READ version versionFile)
string(STRIP ${versionFile} SIMGEAR_VERSION) string(STRIP ${versionFile} SIMGEAR_VERSION)
set(FIND_LIBRARY_USE_LIB64_PATHS ON)
# use simgear version also as the SO version (if building SOs) # use simgear version also as the SO version (if building SOs)
SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION}) SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION})
@@ -21,33 +23,31 @@ if(InSourceBuild)
message(WARNING " mkdir ../sgbuild && cd ../sgbuild && cmake ${CMAKE_SOURCE_DIR}") message(WARNING " mkdir ../sgbuild && cd ../sgbuild && cmake ${CMAKE_SOURCE_DIR}")
endif(InSourceBuild) endif(InSourceBuild)
if (NOT EMBEDDED_SIMGEAR) #packaging
#packaging SET(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/COPYING")
SET(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/COPYING") SET(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README")
SET(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README") SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simulation support libraries for FlightGear and related projects")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simulation support libraries for FlightGear and related projects") SET(CPACK_PACKAGE_VENDOR "The FlightGear project")
SET(CPACK_PACKAGE_VENDOR "The FlightGear project") SET(CPACK_GENERATOR "TBZ2")
SET(CPACK_GENERATOR "TBZ2") SET(CPACK_INSTALL_CMAKE_PROJECTS ${CMAKE_CURRENT_BINARY_DIR};SimGear;ALL;/)
SET(CPACK_INSTALL_CMAKE_PROJECTS ${CMAKE_CURRENT_BINARY_DIR};SimGear;ALL;/)
# split version string into components, note CMAKE_MATCH_0 is the entire regexp match # split version string into components, note CMAKE_MATCH_0 is the entire regexp match
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CPACK_PACKAGE_VERSION ${SIMGEAR_VERSION} ) string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CPACK_PACKAGE_VERSION ${SIMGEAR_VERSION} )
set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1}) set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2}) set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3}) set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
message(STATUS "version is ${CPACK_PACKAGE_VERSION_MAJOR} dot ${CPACK_PACKAGE_VERSION_MINOR} dot ${CPACK_PACKAGE_VERSION_PATCH}") message(STATUS "version is ${CPACK_PACKAGE_VERSION_MAJOR} dot ${CPACK_PACKAGE_VERSION_MINOR} dot ${CPACK_PACKAGE_VERSION_PATCH}")
set(CPACK_SOURCE_GENERATOR TBZ2) set(CPACK_SOURCE_GENERATOR TBZ2)
set(CPACK_SOURCE_PACKAGE_FILE_NAME "simgear-${SIMGEAR_VERSION}" CACHE INTERNAL "tarball basename") set(CPACK_SOURCE_PACKAGE_FILE_NAME "simgear-${SIMGEAR_VERSION}" CACHE INTERNAL "tarball basename")
set(CPACK_SOURCE_IGNORE_FILES set(CPACK_SOURCE_IGNORE_FILES
"^${PROJECT_SOURCE_DIR}/.git;\\\\.gitignore;Makefile.am;~$;${CPACK_SOURCE_IGNORE_FILES}") "^${PROJECT_SOURCE_DIR}/.git;\\\\.gitignore;Makefile.am;~$;${CPACK_SOURCE_IGNORE_FILES}")
message(STATUS "ignoring: ${CPACK_SOURCE_IGNORE_FILES}") message(STATUS "ignoring: ${CPACK_SOURCE_IGNORE_FILES}")
include (CPack) include (CPack)
endif()
# We have some custom .cmake scripts not in the official distribution. # We have some custom .cmake scripts not in the official distribution.
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}") set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}")
@@ -87,13 +87,6 @@ if(NOT "${CMAKE_LIBRARY_ARCHITECTURE}" STREQUAL "")
message(STATUS "additional library directories: ${ADDITIONAL_LIBRARY_PATHS}") message(STATUS "additional library directories: ${ADDITIONAL_LIBRARY_PATHS}")
endif() endif()
if(NOT MSVC)
# TBD: are these really necessary? Aren't they considered by cmake automatically?
list(APPEND ADDITIONAL_LIBRARY_PATHS
/opt/local
/usr/local
/usr)
endif()
##################################################################################### #####################################################################################
if (NOT MSVC) if (NOT MSVC)
@@ -108,8 +101,6 @@ endif()
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF) option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
option(JPEG_FACTORY "Enable JPEG-factory support" OFF) option(JPEG_FACTORY "Enable JPEG-factory support" OFF)
option(SG_SVN_CLIENT "Set to ON to build SimGear with built-in SVN support" OFF)
option(ENABLE_LIBSVN "Set to ON to build SimGear with libsvnclient support" ON)
option(ENABLE_RTI "Set to ON to build SimGear with RTI support" OFF) option(ENABLE_RTI "Set to ON to build SimGear with RTI support" OFF)
option(ENABLE_TESTS "Set to OFF to disable building SimGear's test applications" ON) option(ENABLE_TESTS "Set to OFF to disable building SimGear's test applications" ON)
option(ENABLE_SOUND "Set to OFF to disable building SimGear's sound support" ON) option(ENABLE_SOUND "Set to OFF to disable building SimGear's sound support" ON)
@@ -199,25 +190,6 @@ else()
message(STATUS "JPEG-factory: DISABLED") message(STATUS "JPEG-factory: DISABLED")
endif(JPEG_FACTORY) endif(JPEG_FACTORY)
if (SG_SVN_CLIENT)
message(STATUS "Using built-in subversion client code")
elseif(ENABLE_LIBSVN)
find_package(SvnClient)
if(LIBSVN_FOUND)
message(STATUS "Subversion client support: ENABLED")
set(HAVE_SVN_CLIENT_H 1)
set(HAVE_LIBSVN_CLIENT_1 1)
else()
# Oops. ENABLE_LIBSVN is ON, but svn is still missing.
# Provide clearly visible warning/hint, so builders know what else they should install (or disable).
message(WARNING "Failed to enable subversion client support. Unable to find required subversion client library. Some features may not be available (scenery download).")
message(WARNING "Install 'libsvn' library/DLL (libsvn-devel/libsvnclient/...). Otherwise disable subversion support (set 'ENABLE_LIBSVN' to 'OFF').")
endif(LIBSVN_FOUND)
else()
message(STATUS "Subversion client support: DISABLED")
endif(SG_SVN_CLIENT)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
@@ -225,12 +197,19 @@ if (SYSTEM_EXPAT)
message(STATUS "Requested to use system Expat library, forcing SIMGEAR_SHARED to true") message(STATUS "Requested to use system Expat library, forcing SIMGEAR_SHARED to true")
set(SIMGEAR_SHARED ON) set(SIMGEAR_SHARED ON)
find_package(EXPAT REQUIRED) find_package(EXPAT REQUIRED)
include_directories(${EXPAT_INCLUDE_DIRS})
else() else()
message(STATUS "Using built-in expat code") message(STATUS "Using built-in expat code")
add_definitions(-DHAVE_EXPAT_CONFIG_H) # XML_STATIC is important to avoid sg_expat_external.h
# declaring symbols as declspec(import)
add_definitions(-DHAVE_EXPAT_CONFIG_H -DXML_STATIC)
set(EXPAT_INCLUDE_DIRS
${PROJECT_SOURCE_DIR}/3rdparty/expat
${PROJECT_BINARY_DIR}/3rdparty/expat)
endif(SYSTEM_EXPAT) endif(SYSTEM_EXPAT)
include_directories(${EXPAT_INCLUDE_DIRS})
check_include_file(inttypes.h HAVE_INTTYPES_H) check_include_file(inttypes.h HAVE_INTTYPES_H)
check_include_file(sys/time.h HAVE_SYS_TIME_H) check_include_file(sys/time.h HAVE_SYS_TIME_H)
check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H) check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H)
@@ -279,6 +258,20 @@ if(HAVE_CLOCK_GETTIME)
endif(HAVE_RT) endif(HAVE_RT)
endif(HAVE_CLOCK_GETTIME) endif(HAVE_CLOCK_GETTIME)
set(DL_LIBRARY "")
check_cxx_source_compiles(
"#include <dlfcn.h>
int main(void) {
return 0;
}
"
HAVE_DLFCN_H)
if(HAVE_DLFCN_H)
check_library_exists(dl dlerror "" HAVE_DL)
set(DL_LIBRARY "dl")
endif()
SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually 'd' on windows") SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually 'd' on windows")
SET(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows") SET(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
SET(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows") SET(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
@@ -312,6 +305,14 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(WARNING_FLAGS_C "-Wall") set(WARNING_FLAGS_C "-Wall")
endif() endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# boost goes haywire wrt static asserts
check_cxx_compiler_flag(-Wno-unused-local-typedefs HAS_NOWARN_UNUSED_TYPEDEFS)
if(HAS_NOWARN_UNUSED_TYPEDEFS)
set(WARNING_FLAGS_CXX " ${WARNING_FLAGS_CXX} -Wno-unused-local-typedefs")
endif()
endif()
if(WIN32) if(WIN32)
if(MINGW) if(MINGW)
@@ -344,13 +345,11 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MSVC_LD_FLAGS}")
include_directories(${PROJECT_SOURCE_DIR}) include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include) include_directories(${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include)
include_directories(${PROJECT_BINARY_DIR}/simgear) include_directories(${PROJECT_BINARY_DIR}/simgear)
include_directories(${PROJECT_BINARY_DIR}/simgear/xml)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS} include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}
${OPENAL_INCLUDE_DIR} ${OPENAL_INCLUDE_DIR}
${LibArchive_INCLUDE_DIRS}
) )
add_definitions(-DHAVE_CONFIG_H) add_definitions(-DHAVE_CONFIG_H)
@@ -362,11 +361,6 @@ configure_file (
"${PROJECT_BINARY_DIR}/simgear/simgear_config.h" "${PROJECT_BINARY_DIR}/simgear/simgear_config.h"
) )
configure_file (
"${PROJECT_SOURCE_DIR}/simgear/xml/expat_config_cmake.in"
"${PROJECT_BINARY_DIR}/simgear/xml/expat_config.h"
)
if(ENABLE_TESTS) if(ENABLE_TESTS)
# enable CTest / make test target # enable CTest / make test target
message(STATUS "Tests: ENABLED") message(STATUS "Tests: ENABLED")
@@ -378,31 +372,24 @@ else()
endif(ENABLE_TESTS) endif(ENABLE_TESTS)
# always set TEST_LIBS as it is also used by other tools/applications # always set TEST_LIBS as it is also used by other tools/applications
# TODO maybe better rename? set(TEST_LIBS_INTERNAL_CORE
if(SIMGEAR_SHARED) ${CMAKE_THREAD_LIBS_INIT}
set( TEST_LIBS ${ZLIB_LIBRARY}
SimGearCore) ${WINSOCK_LIBRARY}
else() ${RT_LIBRARY}
set( TEST_LIBS ${DL_LIBRARY}
SimGearCore ${CORE_SERVICES_LIBRARY})
${CMAKE_THREAD_LIBS_INIT} set(TEST_LIBS SimGearCore ${TEST_LIBS_INTERNAL_CORE})
${ZLIB_LIBRARY}
${WINSOCK_LIBRARY}
${RT_LIBRARY}
${CORE_SERVICES_LIBRARY})
endif()
if(NOT SIMGEAR_HEADLESS) if(NOT SIMGEAR_HEADLESS)
set( TEST_LIBS set(TEST_LIBS SimGearScene ${OPENGL_LIBRARIES} ${TEST_LIBS})
SimGearScene
${TEST_LIBS}
${OPENGL_LIBRARIES})
endif() endif()
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/) install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
add_subdirectory(3rdparty)
add_subdirectory(simgear) add_subdirectory(simgear)
if (NOT EMBEDDED_SIMGEAR)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
### uninstall target ### uninstall target
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
@@ -413,5 +400,4 @@ CONFIGURE_FILE(
ADD_CUSTOM_TARGET(uninstall ADD_CUSTOM_TARGET(uninstall
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
endif()
-83
View File
@@ -1,83 +0,0 @@
# Find Subversion client libraries, and dependencies
# including APR (Apache Portable Runtime)
include (CheckFunctionExists)
include (CheckIncludeFile)
include (CheckLibraryExists)
macro(find_static_component comp libs)
# account for alternative Windows svn distribution naming
if(MSVC)
set(compLib "lib${comp}")
else(MSVC)
set(compLib "${comp}")
endif(MSVC)
string(TOUPPER "${comp}" compLibBase)
set( compLibName ${compLibBase}_LIBRARY )
# NO_DEFAULT_PATH is important on Mac - we need to ensure subversion
# libraires in dist/ or Macports are picked over the Apple version
# in /usr, since that's what we will ship.
# On other platforms we do need default paths though, i.e. since Linux
# distros may use architecture-specific directories (like
# /usr/lib/x86_64-linux-gnu) which we cannot hardcode/guess here.
FIND_LIBRARY(${compLibName}
if(APPLE)
NO_DEFAULT_PATH
endif(APPLE)
NAMES ${compLib}
HINTS $ENV{LIBSVN_DIR} ${CMAKE_INSTALL_PREFIX} ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib
PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
PATHS ${ADDITIONAL_LIBRARY_PATHS}
)
list(APPEND ${libs} ${${compLibName}})
endmacro()
find_program(HAVE_APR_CONFIG apr-1-config)
if(HAVE_APR_CONFIG)
execute_process(COMMAND apr-1-config --cppflags --includes
OUTPUT_VARIABLE APR_CFLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND apr-1-config --link-ld
OUTPUT_VARIABLE RAW_APR_LIBS
OUTPUT_STRIP_TRAILING_WHITESPACE)
# clean up some vars, or other CMake pieces complain
string(STRIP "${RAW_APR_LIBS}" APR_LIBS)
else(HAVE_APR_CONFIG)
message(STATUS "apr-1-config not found, implement manual search for APR")
endif(HAVE_APR_CONFIG)
if(HAVE_APR_CONFIG OR MSVC)
find_path(LIBSVN_INCLUDE_DIR svn_client.h
NO_DEFAULT_PATH
HINTS
$ENV{LIBSVN_DIR} ${CMAKE_INSTALL_PREFIX} ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/include
PATH_SUFFIXES include/subversion-1
PATHS
/opt/local
/usr/local
/usr
)
set(LIBSVN_LIBRARIES "")
if (MSVC)
find_static_component("apr-1" LIBSVN_LIBRARIES)
else (MSVC)
list(APPEND LIBSVN_LIBRARIES ${APR_LIBS})
endif (MSVC)
find_static_component("svn_client-1" LIBSVN_LIBRARIES)
find_static_component("svn_subr-1" LIBSVN_LIBRARIES)
find_static_component("svn_ra-1" LIBSVN_LIBRARIES)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBSVN DEFAULT_MSG LIBSVN_LIBRARIES LIBSVN_INCLUDE_DIR)
if(NOT LIBSVN_FOUND)
set(LIBSVN_LIBRARIES "")
endif(NOT LIBSVN_FOUND)
endif(HAVE_APR_CONFIG OR MSVC)
+27 -34
View File
@@ -45,54 +45,29 @@ install (FILES ${HEADERS} DESTINATION include/simgear/)
get_property(coreSources GLOBAL PROPERTY CORE_SOURCES) get_property(coreSources GLOBAL PROPERTY CORE_SOURCES)
get_property(sceneSources GLOBAL PROPERTY SCENE_SOURCES) get_property(sceneSources GLOBAL PROPERTY SCENE_SOURCES)
get_property(publicHeaders GLOBAL PROPERTY PUBLIC_HEADERS) get_property(publicHeaders GLOBAL PROPERTY PUBLIC_HEADERS)
get_property(localExpatSources GLOBAL PROPERTY LOCAL_EXPAT_SOURCES)
if(LIBSVN_FOUND)
add_definitions(${APR_CFLAGS})
include_directories(${LIBSVN_INCLUDE_DIR})
endif()
if(SIMGEAR_SHARED) if(SIMGEAR_SHARED)
message(STATUS "Library building mode: SHARED LIBRARIES") message(STATUS "Library building mode: SHARED LIBRARIES")
add_library(SimGearCore SHARED ${coreSources}) add_library(SimGearCore SHARED ${coreSources} ${localExpatSources})
# set_property(TARGET SimGearCore PROPERTY FRAMEWORK 1)
# message(STATUS "public header: ${publicHeaders}")
# set_property(TARGET SimGearCore PROPERTY PUBLIC_HEADER "${publicHeaders}")
set_property(TARGET SimGearCore PROPERTY LINKER_LANGUAGE CXX) set_property(TARGET SimGearCore PROPERTY LINKER_LANGUAGE CXX)
set_property(TARGET SimGearCore PROPERTY VERSION ${SIMGEAR_VERSION}) set_property(TARGET SimGearCore PROPERTY VERSION ${SIMGEAR_VERSION})
set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION}) set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
install(TARGETS SimGearCore EXPORT SimGearCoreConfig LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
target_link_libraries(SimGearCore ${ZLIB_LIBRARY} ${RT_LIBRARY} install(EXPORT SimGearCoreConfig DESTINATION share/SimGearCore)
${LibArchive_LIBRARIES}
${EXPAT_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${CORE_SERVICES_LIBRARY})
if(LIBSVN_FOUND)
target_link_libraries(SimGearCore ${LIBSVN_LIBRARIES})
endif(LIBSVN_FOUND)
if(NOT SIMGEAR_HEADLESS) if(NOT SIMGEAR_HEADLESS)
add_library(SimGearScene SHARED ${sceneSources}) add_library(SimGearScene SHARED ${sceneSources})
# set_property(TARGET SimGearScene PROPERTY FRAMEWORK 1)
# set_property(TARGET SimGearScene PROPERTY PUBLIC_HEADER "${publicHeaders}")
set_property(TARGET SimGearScene PROPERTY LINKER_LANGUAGE CXX) set_property(TARGET SimGearScene PROPERTY LINKER_LANGUAGE CXX)
set_property(TARGET SimGearScene PROPERTY VERSION ${SIMGEAR_VERSION}) set_property(TARGET SimGearScene PROPERTY VERSION ${SIMGEAR_VERSION})
set_property(TARGET SimGearScene PROPERTY SOVERSION ${SIMGEAR_SOVERSION}) set_property(TARGET SimGearScene PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
target_link_libraries(SimGearScene # EXPORT SimGearSceneConfig
SimGearCore install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} )
${ZLIB_LIBRARY} # install(EXPORT SimGearSceneConfig DESTINATION share/SimGearScene)
${OPENSCENEGRAPH_LIBRARIES}
${OPENAL_LIBRARY}
${OPENGL_LIBRARY}
${JPEG_LIBRARY})
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif() endif()
install(TARGETS SimGearCore LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
else() else()
message(STATUS "Library building mode: STATIC LIBRARIES") message(STATUS "Library building mode: STATIC LIBRARIES")
@@ -114,7 +89,7 @@ else()
source_group("${name}\\Headers" FILES ${g2}) source_group("${name}\\Headers" FILES ${g2})
endforeach() endforeach()
add_library(SimGearCore STATIC ${coreSources}) add_library(SimGearCore STATIC ${coreSources} ${localExpatSources})
install(TARGETS SimGearCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(TARGETS SimGearCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
if(NOT SIMGEAR_HEADLESS) if(NOT SIMGEAR_HEADLESS)
@@ -141,6 +116,24 @@ else()
endif(NOT SIMGEAR_HEADLESS) endif(NOT SIMGEAR_HEADLESS)
endif(SIMGEAR_SHARED) endif(SIMGEAR_SHARED)
target_link_libraries(SimGearCore
${ZLIB_LIBRARY}
${RT_LIBRARY}
${DL_LIBRARY}
${EXPAT_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${CORE_SERVICES_LIBRARY})
if(NOT SIMGEAR_HEADLESS)
target_link_libraries(SimGearScene
SimGearCore
${ZLIB_LIBRARY}
${OPENSCENEGRAPH_LIBRARIES}
${OPENAL_LIBRARY}
${OPENGL_LIBRARY}
${JPEG_LIBRARY})
endif()
if(ENABLE_RTI) if(ENABLE_RTI)
# Ugly first aid to make hla compile agian # Ugly first aid to make hla compile agian
set_property(SOURCE hla/RTI13InteractionClass.cxx hla/RTI13ObjectClass.cxx set_property(SOURCE hla/RTI13InteractionClass.cxx hla/RTI13ObjectClass.cxx
+1 -1
View File
@@ -212,7 +212,7 @@ std::string SGBucket::gen_base_path() const {
main_lat *= -1; main_lat *= -1;
} }
sprintf(raw_path, "%c%03d%c%02d/%c%03d%c%02d", snprintf(raw_path, 256, "%c%03d%c%02d/%c%03d%c%02d",
hem, top_lon, pole, top_lat, hem, top_lon, pole, top_lat,
hem, main_lon, pole, main_lat); hem, main_lon, pole, main_lat);
-2
View File
@@ -4,7 +4,6 @@ set(HEADERS
canvas_fwd.hxx canvas_fwd.hxx
Canvas.hxx Canvas.hxx
CanvasEvent.hxx CanvasEvent.hxx
CanvasEventListener.hxx
CanvasEventManager.hxx CanvasEventManager.hxx
CanvasEventTypes.hxx CanvasEventTypes.hxx
CanvasEventVisitor.hxx CanvasEventVisitor.hxx
@@ -20,7 +19,6 @@ set(HEADERS
set(SOURCES set(SOURCES
Canvas.cxx Canvas.cxx
CanvasEvent.cxx CanvasEvent.cxx
CanvasEventListener.cxx
CanvasEventManager.cxx CanvasEventManager.cxx
CanvasEventVisitor.cxx CanvasEventVisitor.cxx
CanvasMgr.cxx CanvasMgr.cxx
+46 -21
View File
@@ -74,6 +74,12 @@ namespace canvas
setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y); setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y);
} }
//----------------------------------------------------------------------------
Canvas::~Canvas()
{
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Canvas::onDestroy() void Canvas::onDestroy()
{ {
@@ -84,19 +90,6 @@ namespace canvas
} }
} }
//----------------------------------------------------------------------------
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
{
_system_adapter = system_adapter;
_texture.setSystemAdapter(system_adapter);
}
//----------------------------------------------------------------------------
SystemAdapterPtr Canvas::getSystemAdapter() const
{
return _system_adapter;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Canvas::setCanvasMgr(CanvasMgr* canvas_mgr) void Canvas::setCanvasMgr(CanvasMgr* canvas_mgr)
{ {
@@ -221,15 +214,16 @@ namespace canvas
osg::Camera* camera = _texture.getCamera(); osg::Camera* camera = _texture.getCamera();
// TODO Allow custom render order? For now just keep in order with
// property tree.
camera->setRenderOrder(osg::Camera::PRE_RENDER, _node->getIndex());
osg::Vec4 clear_color(0.0f, 0.0f, 0.0f , 1.0f); osg::Vec4 clear_color(0.0f, 0.0f, 0.0f , 1.0f);
parseColor(_node->getStringValue("background"), clear_color); parseColor(_node->getStringValue("background"), clear_color);
camera->setClearColor(clear_color); camera->setClearColor(clear_color);
camera->addChild(_root_group->getMatrixTransform()); camera->addChild(_root_group->getMatrixTransform());
// Ensure objects are drawn in order of traversal
camera->getOrCreateStateSet()->setBinName("TraversalOrderBin");
if( _texture.serviceable() ) if( _texture.serviceable() )
{ {
setStatusFlags(STATUS_OK); setStatusFlags(STATUS_OK);
@@ -319,12 +313,13 @@ namespace canvas
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
naRef Canvas::addEventListener(const nasal::CallContext& ctx) bool Canvas::addEventListener( const std::string& type,
const EventListener& cb )
{ {
if( !_root_group.get() ) if( !_root_group.get() )
naRuntimeError(ctx.c, "Canvas: No root group!"); throw std::runtime_error("Canvas::AddEventListener: no root group!");
return _root_group->addEventListener(ctx); return _root_group->addEventListener(type, cb);
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -419,7 +414,8 @@ namespace canvas
EventVisitor visitor( EventVisitor::TRAVERSE_DOWN, EventVisitor visitor( EventVisitor::TRAVERSE_DOWN,
event->getClientPos(), event->getClientPos(),
event->getDelta() ); event->getDelta(),
_root_group );
if( !_root_group->accept(visitor) ) if( !_root_group->accept(visitor) )
return false; return false;
@@ -585,12 +581,40 @@ namespace canvas
( (
SG_GENERAL, SG_GENERAL,
SG_WARN, SG_WARN,
"Canvas::addPlacementFactory: replace existing factor for type " << type "Canvas::addPlacementFactory: replace existing factory '" << type << "'"
); );
_placement_factories[type] = factory; _placement_factories[type] = factory;
} }
//----------------------------------------------------------------------------
void Canvas::removePlacementFactory(const std::string& type)
{
PlacementFactoryMap::iterator it = _placement_factories.find(type);
if( it == _placement_factories.end() )
SG_LOG
(
SG_GENERAL,
SG_WARN,
"Canvas::removePlacementFactory: no such factory '" << type << "'"
);
else
_placement_factories.erase(it);
}
//----------------------------------------------------------------------------
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
{
_system_adapter = system_adapter;
}
//----------------------------------------------------------------------------
SystemAdapterPtr Canvas::getSystemAdapter()
{
return _system_adapter;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Canvas::setSelf(const PropertyBasedElementPtr& self) void Canvas::setSelf(const PropertyBasedElementPtr& self)
{ {
@@ -632,6 +656,7 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Canvas::PlacementFactoryMap Canvas::_placement_factories; Canvas::PlacementFactoryMap Canvas::_placement_factories;
SystemAdapterPtr Canvas::_system_adapter;
} // namespace canvas } // namespace canvas
} // namespace simgear } // namespace simgear
+17 -7
View File
@@ -29,7 +29,7 @@
#include <osg/NodeCallback> #include <osg/NodeCallback>
#include <osg/observer_ptr> #include <osg/observer_ptr>
#include <memory> #include <boost/scoped_ptr.hpp>
#include <string> #include <string>
namespace simgear namespace simgear
@@ -71,11 +71,9 @@ namespace canvas
typedef osg::ref_ptr<CullCallback> CullCallbackPtr; typedef osg::ref_ptr<CullCallback> CullCallbackPtr;
Canvas(SGPropertyNode* node); Canvas(SGPropertyNode* node);
virtual ~Canvas();
virtual void onDestroy(); virtual void onDestroy();
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
SystemAdapterPtr getSystemAdapter() const;
void setCanvasMgr(CanvasMgr* canvas_mgr); void setCanvasMgr(CanvasMgr* canvas_mgr);
CanvasMgr* getCanvasMgr() const; CanvasMgr* getCanvasMgr() const;
@@ -132,7 +130,7 @@ namespace canvas
void update(double delta_time_sec); void update(double delta_time_sec);
naRef addEventListener(const nasal::CallContext& ctx); bool addEventListener(const std::string& type, const EventListener& cb);
void setSizeX(int sx); void setSizeX(int sx);
void setSizeY(int sy); void setSizeY(int sy);
@@ -162,13 +160,23 @@ namespace canvas
void reloadPlacements( const std::string& type = std::string() ); void reloadPlacements( const std::string& type = std::string() );
static void addPlacementFactory( const std::string& type, static void addPlacementFactory( const std::string& type,
PlacementFactory factory ); PlacementFactory factory );
static void removePlacementFactory(const std::string& type);
/**
* Set global SystemAdapter for all Canvas/ODGauge instances.
*
* The SystemAdapter is responsible for application specific operations
* like loading images/fonts and adding/removing cameras to the scene
* graph.
*/
static void setSystemAdapter(const SystemAdapterPtr& system_adapter);
static SystemAdapterPtr getSystemAdapter();
protected: protected:
SystemAdapterPtr _system_adapter;
CanvasMgr *_canvas_mgr; CanvasMgr *_canvas_mgr;
std::auto_ptr<EventManager> _event_manager; boost::scoped_ptr<EventManager> _event_manager;
int _size_x, int _size_x,
_size_y, _size_y,
@@ -202,6 +210,8 @@ namespace canvas
private: private:
static SystemAdapterPtr _system_adapter;
Canvas(const Canvas&); // = delete; Canvas(const Canvas&); // = delete;
Canvas& operator=(const Canvas&); // = delete; Canvas& operator=(const Canvas&); // = delete;
}; };
+6
View File
@@ -62,6 +62,12 @@ namespace canvas
return target; return target;
} }
//----------------------------------------------------------------------------
ElementWeakPtr Event::getCurrentTarget() const
{
return current_target;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
double Event::getTime() const double Event::getTime() const
{ {
+3 -1
View File
@@ -41,7 +41,8 @@ namespace canvas
}; };
Type type; Type type;
ElementWeakPtr target; ElementWeakPtr target,
current_target;
double time; double time;
bool propagation_stopped; bool propagation_stopped;
@@ -55,6 +56,7 @@ namespace canvas
std::string getTypeString() const; std::string getTypeString() const;
ElementWeakPtr getTarget() const; ElementWeakPtr getTarget() const;
ElementWeakPtr getCurrentTarget() const;
double getTime() const; double getTime() const;
-70
View File
@@ -1,70 +0,0 @@
// Listener for canvas (GUI) events being passed to a Nasal function/code
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "CanvasEvent.hxx"
#include "CanvasEventListener.hxx"
#include "CanvasSystemAdapter.hxx"
#include <simgear/nasal/cppbind/Ghost.hxx>
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
EventListener::EventListener(naRef code, const SystemAdapterPtr& sys_adapter):
_code(code),
_gc_key(-1),
_sys(sys_adapter)
{
assert( sys_adapter );
if( !naIsCode(code)
&& !naIsCCode(code)
&& !naIsFunc(code) )
throw std::runtime_error
(
"canvas::EventListener: invalid function argument"
);
_gc_key = sys_adapter->gcSave(_code);
}
//----------------------------------------------------------------------------
EventListener::~EventListener()
{
assert( !_sys.expired() );
_sys.lock()->gcRelease(_gc_key);
}
//----------------------------------------------------------------------------
void EventListener::call(const canvas::EventPtr& event)
{
SystemAdapterPtr sys = _sys.lock();
naRef args[] = {
nasal::Ghost<EventPtr>::create(sys->getNasalContext(), event)
};
const int num_args = sizeof(args)/sizeof(args[0]);
sys->callMethod(_code, naNil(), num_args, args, naNil());
}
} // namespace canvas
} // namespace simgear
+64 -21
View File
@@ -19,6 +19,7 @@
#include "CanvasEventManager.hxx" #include "CanvasEventManager.hxx"
#include "MouseEvent.hxx" #include "MouseEvent.hxx"
#include <simgear/canvas/elements/CanvasElement.hxx> #include <simgear/canvas/elements/CanvasElement.hxx>
#include <cmath>
namespace simgear namespace simgear
{ {
@@ -71,6 +72,7 @@ namespace canvas
bool EventManager::handleEvent( const MouseEventPtr& event, bool EventManager::handleEvent( const MouseEventPtr& event,
const EventPropagationPath& path ) const EventPropagationPath& path )
{ {
bool handled = false;
switch( event->type ) switch( event->type )
{ {
case Event::MOUSE_DOWN: case Event::MOUSE_DOWN:
@@ -78,20 +80,27 @@ namespace canvas
break; break;
case Event::MOUSE_UP: case Event::MOUSE_UP:
{ {
if( _last_mouse_down.path.empty() ) // If the mouse has moved while a button was down (aka. dragging) we
// Ignore mouse up without any previous mouse down // need to notify the original element that the mouse has left it, and
return false; // the new element that it has been entered
if( _last_mouse_down.path != path )
handled |= handleMove(event, path);
// normal mouseup // normal mouseup
propagateEvent(event, path); handled |= propagateEvent(event, path);
if( _last_mouse_down.path.empty() )
// Ignore mouse up without any previous mouse down
return handled;
// now handle click/dblclick // now handle click/dblclick
if( checkClickDistance(path, _last_mouse_down.path) ) if( checkClickDistance(path, _last_mouse_down.path) )
handleClick(event, getCommonAncestor(_last_mouse_down.path, path)); handled |=
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
_last_mouse_down.clear(); _last_mouse_down.clear();
return true; return handled;
} }
case Event::DRAG: case Event::DRAG:
if( !_last_mouse_down.valid() ) if( !_last_mouse_down.valid() )
@@ -99,11 +108,18 @@ namespace canvas
else else
return propagateEvent(event, _last_mouse_down.path); return propagateEvent(event, _last_mouse_down.path);
case Event::MOUSE_MOVE: case Event::MOUSE_MOVE:
handleMove(event, path); handled |= handleMove(event, path);
break; break;
case Event::MOUSE_LEAVE: case Event::MOUSE_LEAVE:
// Mouse leaves window and therefore also current mouseover element // Mouse leaves window and therefore also current mouseover element
handleMove(event, EventPropagationPath()); handleMove(event, EventPropagationPath());
// Event is only send if mouse is moved outside the window or dragging
// has ended somewhere outside the window. In both cases a mouse button
// has been released, so no more mouse down or click...
_last_mouse_down.clear();
_last_click.clear();
return true; return true;
case Event::WHEEL: case Event::WHEEL:
break; break;
@@ -111,11 +127,11 @@ namespace canvas
return false; return false;
} }
return propagateEvent(event, path); return handled | propagateEvent(event, path);
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void EventManager::handleClick( const MouseEventPtr& event, bool EventManager::handleClick( const MouseEventPtr& event,
const EventPropagationPath& path ) const EventPropagationPath& path )
{ {
MouseEventPtr click(new MouseEvent(*event)); MouseEventPtr click(new MouseEvent(*event));
@@ -145,28 +161,33 @@ namespace canvas
dbl_click->type = Event::DBL_CLICK; dbl_click->type = Event::DBL_CLICK;
} }
propagateEvent(click, path); bool handled = propagateEvent(click, path);
if( dbl_click ) if( dbl_click )
propagateEvent(dbl_click, getCommonAncestor(_last_click.path, path)); handled |= propagateEvent( dbl_click,
getCommonAncestor(_last_click.path, path) );
_last_click = StampedPropagationPath(path, event->getTime()); _last_click = StampedPropagationPath(path, event->getTime());
return handled;
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void EventManager::handleMove( const MouseEventPtr& event, bool EventManager::handleMove( const MouseEventPtr& event,
const EventPropagationPath& path ) const EventPropagationPath& path )
{ {
EventPropagationPath& last_path = _last_mouse_over.path; EventPropagationPath& last_path = _last_mouse_over.path;
if( last_path == path ) if( last_path == path )
return; return false;
bool handled = false;
// Leave old element // Leave old element
if( !last_path.empty() ) if( !last_path.empty() )
{ {
MouseEventPtr mouseout(new MouseEvent(*event)); MouseEventPtr mouseout(new MouseEvent(*event));
mouseout->type = Event::MOUSE_OUT; mouseout->type = Event::MOUSE_OUT;
propagateEvent(mouseout, last_path); handled |= propagateEvent(mouseout, last_path);
// Send a mouseleave event to all ancestors of the currently left element // Send a mouseleave event to all ancestors of the currently left element
// which are not ancestor of the new element currently entered // which are not ancestor of the new element currently entered
@@ -178,7 +199,7 @@ namespace canvas
MouseEventPtr mouseleave(new MouseEvent(*event)); MouseEventPtr mouseleave(new MouseEvent(*event));
mouseleave->type = Event::MOUSE_LEAVE; mouseleave->type = Event::MOUSE_LEAVE;
propagateEvent(mouseleave, path_leave); handled |= propagateEvent(mouseleave, path_leave);
path_leave.pop_back(); path_leave.pop_back();
} }
@@ -189,7 +210,7 @@ namespace canvas
{ {
MouseEventPtr mouseover(new MouseEvent(*event)); MouseEventPtr mouseover(new MouseEvent(*event));
mouseover->type = Event::MOUSE_OVER; mouseover->type = Event::MOUSE_OVER;
propagateEvent(mouseover, path); handled |= propagateEvent(mouseover, path);
// Send a mouseenter event to all ancestors of the currently entered // Send a mouseenter event to all ancestors of the currently entered
// element which are not ancestor of the old element currently being // element which are not ancestor of the old element currently being
@@ -204,11 +225,12 @@ namespace canvas
MouseEventPtr mouseenter(new MouseEvent(*event)); MouseEventPtr mouseenter(new MouseEvent(*event));
mouseenter->type = Event::MOUSE_ENTER; mouseenter->type = Event::MOUSE_ENTER;
propagateEvent(mouseenter, path_enter); handled |= propagateEvent(mouseenter, path_enter);
} }
} }
_last_mouse_over.path = path; _last_mouse_over.path = path;
return handled;
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -230,6 +252,21 @@ namespace canvas
// std::cout << it->element.lock()->getProps()->getPath() << std::endl; // std::cout << it->element.lock()->getProps()->getPath() << std::endl;
// } // }
// Check if event supports bubbling
const Event::Type types_no_bubbling[] = {
Event::MOUSE_ENTER,
Event::MOUSE_LEAVE,
};
const size_t num_types_no_bubbling = sizeof(types_no_bubbling)
/ sizeof(types_no_bubbling[0]);
bool do_bubble = true;
for( size_t i = 0; i < num_types_no_bubbling; ++i )
if( event->type == types_no_bubbling[i] )
{
do_bubble = false;
break;
}
// Bubbling phase // Bubbling phase
for( EventPropagationPath::const_reverse_iterator for( EventPropagationPath::const_reverse_iterator
it = path.rbegin(); it = path.rbegin();
@@ -239,9 +276,14 @@ namespace canvas
ElementPtr el = it->element.lock(); ElementPtr el = it->element.lock();
if( !el ) if( !el )
{
// Ignore element if it has been destroyed while traversing the event // Ignore element if it has been destroyed while traversing the event
// (eg. removed by another event handler) // (eg. removed by another event handler)
continue; if( do_bubble )
continue;
else
break;
}
// TODO provide functions to convert delta to local coordinates on demand. // TODO provide functions to convert delta to local coordinates on demand.
// Maybe also provide a clone method for events as local coordinates // Maybe also provide a clone method for events as local coordinates
@@ -258,9 +300,10 @@ namespace canvas
//mouse_event->delta = it->local_delta; //mouse_event->delta = it->local_delta;
} }
event->current_target = el;
el->handleEvent(event); el->handleEvent(event);
if( event->propagation_stopped ) if( event->propagation_stopped || !do_bubble )
return true; return true;
} }
@@ -273,8 +316,8 @@ namespace canvas
const EventPropagationPath& path2 ) const const EventPropagationPath& path2 ) const
{ {
osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos; osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos;
return delta.x() < drag_threshold return std::fabs(delta.x()) < drag_threshold
&& delta.y() < drag_threshold; && std::fabs(delta.y()) < drag_threshold;
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
+2 -2
View File
@@ -70,13 +70,13 @@ namespace canvas
/** /**
* Propagate click event and handle multi-click (eg. create dblclick) * Propagate click event and handle multi-click (eg. create dblclick)
*/ */
void handleClick( const MouseEventPtr& event, bool handleClick( const MouseEventPtr& event,
const EventPropagationPath& path ); const EventPropagationPath& path );
/** /**
* Handle mouseover/enter/out/leave * Handle mouseover/enter/out/leave
*/ */
void handleMove( const MouseEventPtr& event, bool handleMove( const MouseEventPtr& event,
const EventPropagationPath& path ); const EventPropagationPath& path );
bool propagateEvent( const EventPtr& event, bool propagateEvent( const EventPtr& event,
+10 -7
View File
@@ -30,8 +30,10 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
EventVisitor::EventVisitor( TraverseMode mode, EventVisitor::EventVisitor( TraverseMode mode,
const osg::Vec2f& pos, const osg::Vec2f& pos,
const osg::Vec2f& delta ): const osg::Vec2f& delta,
_traverse_mode( mode ) const ElementPtr& root ):
_traverse_mode( mode ),
_root(root)
{ {
if( mode == TRAVERSE_DOWN ) if( mode == TRAVERSE_DOWN )
{ {
@@ -70,10 +72,11 @@ namespace canvas
m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1) m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1)
); );
// Don't check collision with root element (2nd element in _target_path) // Don't check specified root element for collision, as its purpose is to
// do event listeners attached to the canvas itself (its root group) // catch all events which have no target. This allows for example calling
// always get called even if no element has been hit. // event listeners attached to the canvas itself (its root group) even if
if( _target_path.size() > 1 && !el.hitBound(pos, local_pos) ) // no element has been hit.
if( _root.get() != &el && !el.hitBound(pos, local_pos) )
return false; return false;
const osg::Vec2f& delta = _target_path.back().local_delta; const osg::Vec2f& delta = _target_path.back().local_delta;
@@ -86,7 +89,7 @@ namespace canvas
EventTarget target = {el.getWeakPtr(), local_pos, local_delta}; EventTarget target = {el.getWeakPtr(), local_pos, local_delta};
_target_path.push_back(target); _target_path.push_back(target);
if( el.traverse(*this) || _target_path.size() <= 2 ) if( el.traverse(*this) || &el == _root.get() )
return true; return true;
_target_path.pop_back(); _target_path.pop_back();
+10 -1
View File
@@ -38,9 +38,17 @@ namespace canvas
TRAVERSE_DOWN TRAVERSE_DOWN
}; };
/**
*
* @param mode
* @param pos Mouse position
* @param delta Mouse movement since last mouse move event
* @param root Element to dispatch events to if no element is hit
*/
EventVisitor( TraverseMode mode, EventVisitor( TraverseMode mode,
const osg::Vec2f& pos, const osg::Vec2f& pos,
const osg::Vec2f& delta ); const osg::Vec2f& delta,
const ElementPtr& root = ElementPtr() );
virtual ~EventVisitor(); virtual ~EventVisitor();
virtual bool traverse(Element& el); virtual bool traverse(Element& el);
virtual bool apply(Element& el); virtual bool apply(Element& el);
@@ -51,6 +59,7 @@ namespace canvas
TraverseMode _traverse_mode; TraverseMode _traverse_mode;
EventPropagationPath _target_path; EventPropagationPath _target_path;
ElementPtr _root;
}; };
+3 -5
View File
@@ -18,6 +18,7 @@
#include "CanvasMgr.hxx" #include "CanvasMgr.hxx"
#include "Canvas.hxx" #include "Canvas.hxx"
#include "CanvasEventManager.hxx"
#include <boost/bind.hpp> #include <boost/bind.hpp>
@@ -35,10 +36,8 @@ namespace canvas
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
CanvasMgr::CanvasMgr( SGPropertyNode_ptr node, CanvasMgr::CanvasMgr(SGPropertyNode_ptr node):
SystemAdapterPtr system_adapter ): PropertyBasedMgr(node, "texture", &canvasFactory)
PropertyBasedMgr(node, "texture", &canvasFactory),
_system_adapter(system_adapter)
{ {
} }
@@ -65,7 +64,6 @@ namespace canvas
void CanvasMgr::elementCreated(PropertyBasedElementPtr element) void CanvasMgr::elementCreated(PropertyBasedElementPtr element)
{ {
CanvasPtr canvas = boost::static_pointer_cast<Canvas>(element); CanvasPtr canvas = boost::static_pointer_cast<Canvas>(element);
canvas->setSystemAdapter(_system_adapter);
canvas->setCanvasMgr(this); canvas->setCanvasMgr(this);
} }
+1 -7
View File
@@ -34,12 +34,8 @@ namespace canvas
/** /**
* @param node Root node of branch used to control canvasses * @param node Root node of branch used to control canvasses
* @param system_adapter Adapter for connecting between canvas and
* application framework
*
*/ */
CanvasMgr( SGPropertyNode_ptr node, CanvasMgr(SGPropertyNode_ptr node);
SystemAdapterPtr system_adapter );
/** /**
* Create a new canvas * Create a new canvas
@@ -65,8 +61,6 @@ namespace canvas
protected: protected:
SystemAdapterPtr _system_adapter;
virtual void elementCreated(PropertyBasedElementPtr element); virtual void elementCreated(PropertyBasedElementPtr element);
}; };
-24
View File
@@ -20,7 +20,6 @@
#define SG_CANVAS_SYSTEM_ADAPTER_HXX_ #define SG_CANVAS_SYSTEM_ADAPTER_HXX_
#include "canvas_fwd.hxx" #include "canvas_fwd.hxx"
#include <simgear/nasal/nasal.h>
namespace simgear namespace simgear
{ {
@@ -36,29 +35,6 @@ namespace canvas
virtual void addCamera(osg::Camera* camera) const = 0; virtual void addCamera(osg::Camera* camera) const = 0;
virtual void removeCamera(osg::Camera* camera) const = 0; virtual void removeCamera(osg::Camera* camera) const = 0;
virtual osg::Image* getImage(const std::string& path) const = 0; virtual osg::Image* getImage(const std::string& path) const = 0;
virtual naContext getNasalContext() const = 0;
/**
* Save passed reference to Nasal object from being deleted by the
* garbage collector.
*/
virtual int gcSave(naRef r) = 0;
/**
* Release an object previously passed to ::gcSave to allow it being
* cleaned up by the garbage collector.
*/
virtual void gcRelease(int key) = 0;
/**
* Call a Nasal function with the given environment and arguments.
*/
virtual naRef callMethod( naRef code,
naRef self,
int argc,
naRef* args,
naRef locals ) = 0;
}; };
} // namespace canvas } // namespace canvas
+58 -10
View File
@@ -29,6 +29,7 @@
#endif #endif
#include "ODGauge.hxx" #include "ODGauge.hxx"
#include "Canvas.hxx"
#include "CanvasSystemAdapter.hxx" #include "CanvasSystemAdapter.hxx"
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
@@ -43,6 +44,7 @@
#include <osg/ShadeModel> #include <osg/ShadeModel>
#include <osg/StateSet> #include <osg/StateSet>
#include <osg/FrameBufferObject> // for GL_DEPTH_STENCIL_EXT on Windows #include <osg/FrameBufferObject> // for GL_DEPTH_STENCIL_EXT on Windows
#include <osgUtil/RenderBin>
#include <cassert> #include <cassert>
@@ -51,6 +53,58 @@ namespace simgear
namespace canvas namespace canvas
{ {
class PreOrderBin:
public osgUtil::RenderBin
{
public:
PreOrderBin()
{}
PreOrderBin( const RenderBin& rhs,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY ):
RenderBin(rhs, copyop)
{}
virtual osg::Object* cloneType() const
{
return new PreOrderBin();
}
virtual osg::Object* clone(const osg::CopyOp& copyop) const
{
return new PreOrderBin(*this,copyop);
}
virtual bool isSameKindAs(const osg::Object* obj) const
{
return dynamic_cast<const PreOrderBin*>(obj) != 0L;
}
virtual const char* className() const
{
return "PreOrderBin";
}
virtual void sort()
{
// Do not sort to keep traversal order...
}
};
#ifndef OSG_INIT_SINGLETON_PROXY
/**
* http://svn.openscenegraph.org/osg/OpenSceneGraph/trunk/include/osg/Object
*
* Helper macro that creates a static proxy object to call singleton function
* on it's construction, ensuring that the singleton gets initialized at
* startup.
*/
# define OSG_INIT_SINGLETON_PROXY(ProxyName, Func)\
static struct ProxyName{ ProxyName() { Func; } } s_##ProxyName;
#endif
OSG_INIT_SINGLETON_PROXY(
PreOrderBinProxy,
(osgUtil::RenderBin::addRenderBinPrototype("PreOrderBin", new PreOrderBin))
);
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
ODGauge::ODGauge(): ODGauge::ODGauge():
_size_x( -1 ), _size_x( -1 ),
@@ -70,12 +124,6 @@ namespace canvas
clear(); clear();
} }
//----------------------------------------------------------------------------
void ODGauge::setSystemAdapter(const SystemAdapterPtr& system_adapter)
{
_system_adapter = system_adapter;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void ODGauge::setSize(int size_x, int size_y) void ODGauge::setSize(int size_x, int size_y)
{ {
@@ -209,8 +257,8 @@ namespace canvas
updateSampling(); updateSampling();
updateBlendMode(); updateBlendMode();
if( _system_adapter ) if( Canvas::getSystemAdapter() )
_system_adapter->addCamera(camera.get()); Canvas::getSystemAdapter()->addCamera(camera.get());
_flags |= AVAILABLE; _flags |= AVAILABLE;
} }
@@ -226,8 +274,8 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void ODGauge::clear() void ODGauge::clear()
{ {
if( camera.valid() && _system_adapter ) if( camera.valid() && Canvas::getSystemAdapter() )
_system_adapter->removeCamera(camera.get()); Canvas::getSystemAdapter()->removeCamera(camera.get());
camera.release(); camera.release();
texture.release(); texture.release();
-4
View File
@@ -53,8 +53,6 @@ namespace canvas
ODGauge(); ODGauge();
virtual ~ODGauge(); virtual ~ODGauge();
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
/** /**
* Set the size of the render target. * Set the size of the render target.
* *
@@ -136,8 +134,6 @@ namespace canvas
protected: protected:
SystemAdapterPtr _system_adapter;
int _size_x, int _size_x,
_size_y, _size_y,
_view_width, _view_width,
+2 -1
View File
@@ -51,7 +51,6 @@ namespace canvas
SG_FWD_DECL(Text) SG_FWD_DECL(Text)
SG_FWD_DECL(Event) SG_FWD_DECL(Event)
SG_FWD_DECL(EventListener)
SG_FWD_DECL(MouseEvent) SG_FWD_DECL(MouseEvent)
SG_FWD_DECL(Placement) SG_FWD_DECL(Placement)
SG_FWD_DECL(SystemAdapter) SG_FWD_DECL(SystemAdapter)
@@ -73,6 +72,8 @@ namespace canvas
typedef boost::function<Placements( SGPropertyNode*, typedef boost::function<Placements( SGPropertyNode*,
CanvasPtr )> PlacementFactory; CanvasPtr )> PlacementFactory;
typedef boost::function<void(const EventPtr&)> EventListener;
} // namespace canvas } // namespace canvas
} // namespace simgear } // namespace simgear
+338 -147
View File
@@ -17,10 +17,10 @@
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "CanvasElement.hxx" #include "CanvasElement.hxx"
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasEventListener.hxx>
#include <simgear/canvas/CanvasEventVisitor.hxx> #include <simgear/canvas/CanvasEventVisitor.hxx>
#include <simgear/canvas/MouseEvent.hxx> #include <simgear/canvas/MouseEvent.hxx>
#include <simgear/math/SGMisc.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/scene/material/parseBlendFunc.hxx> #include <simgear/scene/material/parseBlendFunc.hxx>
#include <osg/Drawable> #include <osg/Drawable>
@@ -29,11 +29,10 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp> #include <boost/make_shared.hpp>
#include <boost/tokenizer.hpp>
#include <cassert> #include <cassert>
#include <cmath>
#include <cstring> #include <cstring>
namespace simgear namespace simgear
@@ -42,6 +41,63 @@ namespace canvas
{ {
const std::string NAME_TRANSFORM = "tf"; const std::string NAME_TRANSFORM = "tf";
/**
* glScissor with coordinates relative to different reference frames.
*/
class Element::RelativeScissor:
public osg::Scissor
{
public:
ReferenceFrame _coord_reference;
osg::Matrix _parent_inverse;
RelativeScissor():
_coord_reference(GLOBAL)
{}
virtual void apply(osg::State& state) const
{
const osg::Viewport* vp = state.getCurrentViewport();
float w2 = 0.5 * vp->width(),
h2 = 0.5 * vp->height();
osg::Matrix model_view
(
w2, 0, 0, 0,
0, h2, 0, 0,
0, 0, 1, 0,
w2, h2, 0, 1
);
model_view.preMult(state.getProjectionMatrix());
if( _coord_reference != GLOBAL )
{
model_view.preMult(state.getModelViewMatrix());
if( _coord_reference == PARENT )
model_view.preMult(_parent_inverse);
}
const osg::Vec2 scale( model_view(0,0), model_view(1,1)),
offset(model_view(3,0), model_view(3,1));
// TODO check/warn for rotation?
GLint x = SGMiscf::roundToInt(scale.x() * _x + offset.x()),
y = SGMiscf::roundToInt(scale.y() * _y + offset.y()),
w = SGMiscf::roundToInt(std::fabs(scale.x()) * _width),
h = SGMiscf::roundToInt(std::fabs(scale.y()) * _height);
if( scale.x() < 0 )
x -= w;
if( scale.y() < 0 )
y -= h;
glScissor(x, y, w, h);
}
};
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Element::OSGUserData::OSGUserData(ElementPtr element): Element::OSGUserData::OSGUserData(ElementPtr element):
element(element) element(element)
@@ -52,7 +108,19 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Element::~Element() Element::~Element()
{ {
if( !_transform.valid() )
return;
for(unsigned int i = 0; i < _transform->getNumChildren(); ++i)
{
OSGUserData* ud =
static_cast<OSGUserData*>(_transform->getChild(i)->getUserData());
if( ud )
// Ensure parent is cleared to prevent accessing released memory if an
// element somehow survives longer than his parent.
ud->element->_parent = 0;
}
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -86,6 +154,12 @@ namespace canvas
return boost::static_pointer_cast<Element>(_self.lock()); return boost::static_pointer_cast<Element>(_self.lock());
} }
//----------------------------------------------------------------------------
ElementPtr Element::getParent()
{
return _parent ? _parent->getWeakPtr().lock() : ElementPtr();
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Element::update(double dt) void Element::update(double dt)
{ {
@@ -93,64 +167,15 @@ namespace canvas
// Don't do anything if element is hidden // Don't do anything if element is hidden
return; return;
if( _transform_dirty ) // Trigger matrix update
getMatrix();
if( _attributes_dirty & SCISSOR_COORDS )
{ {
osg::Matrix m; if( _scissor && _scissor->_coord_reference != GLOBAL )
for( size_t i = 0; i < _transform_types.size(); ++i ) _scissor->_parent_inverse = _transform->getInverseMatrix();
{
// Skip unused indizes...
if( _transform_types[i] == TT_NONE )
continue;
SGPropertyNode* tf_node = _node->getChild("tf", i, true); _attributes_dirty &= ~SCISSOR_COORDS;
// Build up the matrix representation of the current transform node
osg::Matrix tf;
switch( _transform_types[i] )
{
case TT_MATRIX:
tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
tf_node->getDoubleValue("m[1]", 0),
0,
tf_node->getDoubleValue("m[6]", 0),
tf_node->getDoubleValue("m[2]", 0),
tf_node->getDoubleValue("m[3]", 1),
0,
tf_node->getDoubleValue("m[7]", 0),
0,
0,
1,
0,
tf_node->getDoubleValue("m[4]", 0),
tf_node->getDoubleValue("m[5]", 0),
0,
tf_node->getDoubleValue("m[8]", 1) );
break;
case TT_TRANSLATE:
tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
tf_node->getDoubleValue("t[1]", 0),
0 ) );
break;
case TT_ROTATE:
tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
break;
case TT_SCALE:
{
float sx = tf_node->getDoubleValue("s[0]", 1);
// sy defaults to sx...
tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
break;
}
default:
break;
}
m.postMult( tf );
}
_transform->setMatrix(m);
_transform_dirty = false;
} }
// Update bounding box on manual update (manual updates pass zero dt) // Update bounding box on manual update (manual updates pass zero dt)
@@ -173,31 +198,28 @@ namespace canvas
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
naRef Element::addEventListener(const nasal::CallContext& ctx) bool Element::addEventListener( const std::string& type_str,
const EventListener& cb )
{ {
const std::string type_str = ctx.requireArg<std::string>(0);
naRef code = ctx.requireArg<naRef>(1);
SG_LOG SG_LOG
( (
SG_NASAL, SG_GENERAL,
SG_INFO, SG_INFO,
"addEventListener(" << _node->getPath() << ", " << type_str << ")" "addEventListener(" << _node->getPath() << ", " << type_str << ")"
); );
Event::Type type = Event::strToType(type_str); Event::Type type = Event::strToType(type_str);
if( type == Event::UNKNOWN ) if( type == Event::UNKNOWN )
naRuntimeError( ctx.c, {
"addEventListener: Unknown event type %s", SG_LOG( SG_GENERAL,
type_str.c_str() ); SG_WARN,
"addEventListener: Unknown event type " << type_str );
return false;
}
_listener[ type ].push_back _listener[ type ].push_back(cb);
(
boost::make_shared<EventListener>( code,
_canvas.lock()->getSystemAdapter() )
);
return naNil(); return true;
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -209,7 +231,7 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
bool Element::accept(EventVisitor& visitor) bool Element::accept(EventVisitor& visitor)
{ {
if( !_transform.valid() ) if( !isVisible() )
return false; return false;
return visitor.apply(*this); return visitor.apply(*this);
@@ -236,8 +258,8 @@ namespace canvas
if( listeners == _listener.end() ) if( listeners == _listener.end() )
return false; return false;
BOOST_FOREACH(EventListenerPtr listener, listeners->second) BOOST_FOREACH(EventListener const& listener, listeners->second)
listener->call(event); listener(event);
return true; return true;
} }
@@ -259,7 +281,7 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
bool Element::isVisible() const bool Element::isVisible() const
{ {
return _transform->getNodeMask() != 0; return _transform.valid() && _transform->getNodeMask() != 0;
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -284,7 +306,7 @@ namespace canvas
_transform_types.resize( child->getIndex() + 1 ); _transform_types.resize( child->getIndex() + 1 );
_transform_types[ child->getIndex() ] = TT_NONE; _transform_types[ child->getIndex() ] = TT_NONE;
_transform_dirty = true; _attributes_dirty |= TRANSFORM;
return; return;
} }
else if( parent->getParent() == _node else if( parent->getParent() == _node
@@ -305,7 +327,7 @@ namespace canvas
else if( name == "s" ) else if( name == "s" )
type = TT_SCALE; type = TT_SCALE;
_transform_dirty = true; _attributes_dirty |= TRANSFORM;
return; return;
} }
@@ -315,29 +337,37 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child) void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
{ {
if( parent == _node && child->getNameString() == NAME_TRANSFORM ) if( parent == _node )
{ {
if( !_transform.valid() ) if( child->getNameString() == NAME_TRANSFORM )
return;
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
{ {
SG_LOG if( !_transform.valid() )
( return;
SG_GENERAL,
SG_WARN, if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
"Element::childRemoved: unknown transform: " << child->getPath() {
); SG_LOG
(
SG_GENERAL,
SG_WARN,
"Element::childRemoved: unknown transform: " << child->getPath()
);
return;
}
_transform_types[ child->getIndex() ] = TT_NONE;
while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
_transform_types.pop_back();
_attributes_dirty |= TRANSFORM;
return; return;
} }
else if( StyleInfo const* style = getStyleInfo(child->getNameString()) )
_transform_types[ child->getIndex() ] = TT_NONE; {
if( setStyle(getParentStyle(child), style) )
while( !_transform_types.empty() && _transform_types.back() == TT_NONE ) return;
_transform_types.pop_back(); }
_transform_dirty = true;
return;
} }
childRemoved(child); childRemoved(child);
@@ -350,8 +380,17 @@ namespace canvas
if( parent == _node ) if( parent == _node )
{ {
const std::string& name = child->getNameString(); const std::string& name = child->getNameString();
if( setStyle(child) ) if( StyleInfo const* style_info = getStyleInfo(name) )
{
SGPropertyNode const* style = child;
if( isStyleEmpty(child) )
{
child->clearValue();
style = getParentStyle(child);
}
setStyle(style, style_info);
return; return;
}
else if( name == "update" ) else if( name == "update" )
return update(0); return update(0);
else if( name == "visible" ) else if( name == "visible" )
@@ -363,7 +402,7 @@ namespace canvas
else if( parent->getParent() == _node else if( parent->getParent() == _node
&& parent->getNameString() == NAME_TRANSFORM ) && parent->getNameString() == NAME_TRANSFORM )
{ {
_transform_dirty = true; _attributes_dirty |= TRANSFORM;
return; return;
} }
@@ -371,21 +410,10 @@ namespace canvas
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
bool Element::setStyle(const SGPropertyNode* child) bool Element::setStyle( const SGPropertyNode* child,
const StyleInfo* style_info )
{ {
StyleSetters::const_iterator setter = return canApplyStyle(child) && setStyleImpl(child, style_info);
_style_setters.find(child->getNameString());
if( setter == _style_setters.end() )
return false;
const StyleSetter* style_setter = &setter->second.setter;
while( style_setter )
{
if( style_setter->func(*this, child) )
return true;
style_setter = style_setter->next;
}
return false;
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -394,6 +422,7 @@ namespace canvas
if( clip.empty() || clip == "auto" ) if( clip.empty() || clip == "auto" )
{ {
getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR); getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR);
_scissor = 0;
return; return;
} }
@@ -406,17 +435,22 @@ namespace canvas
return; return;
} }
typedef boost::tokenizer<boost::char_separator<char> > tokenizer; const std::string sep(", \t\npx");
const boost::char_separator<char> del(", \t\npx");
tokenizer tokens(clip.begin() + RECT.size(), clip.end() - 1, del);
int comp = 0; int comp = 0;
int values[4]; float values[4];
for( tokenizer::const_iterator tok = tokens.begin();
tok != tokens.end() && comp < 4; for(size_t pos = RECT.size(); comp < 4; ++comp)
++tok, ++comp )
{ {
values[comp] = boost::lexical_cast<int>(*tok); pos = clip.find_first_not_of(sep, pos);
if( pos == std::string::npos || pos == clip.size() - 1 )
break;
char *end = 0;
values[comp] = strtod(&clip[pos], &end);
if( end == &clip[pos] || !end )
break;
pos = end - &clip[0];
} }
if( comp < 4 ) if( comp < 4 )
@@ -425,32 +459,37 @@ namespace canvas
return; return;
} }
float scale_x = 1, float width = values[1] - values[3],
scale_y = 1; height = values[2] - values[0];
CanvasPtr canvas = _canvas.lock(); if( width < 0 || height < 0 )
if( canvas )
{ {
// The scissor rectangle isn't affected by any transformation, so we need SG_LOG(SG_GENERAL, SG_WARN, "Canvas: negative clip size: " << clip);
// to convert to image/canvas coordinates on our selves. return;
scale_x = canvas->getSizeX()
/ static_cast<float>(canvas->getViewWidth());
scale_y = canvas->getSizeY()
/ static_cast<float>(canvas->getViewHeight());
} }
osg::Scissor* scissor = new osg::Scissor(); _scissor = new RelativeScissor();
// <top>, <right>, <bottom>, <left> // <top>, <right>, <bottom>, <left>
scissor->x() = scale_x * values[3]; _scissor->x() = SGMiscf::roundToInt(values[3]);
scissor->y() = scale_y * values[0]; _scissor->y() = SGMiscf::roundToInt(values[0]);
scissor->width() = scale_x * (values[1] - values[3]); _scissor->width() = SGMiscf::roundToInt(width);
scissor->height() = scale_y * (values[2] - values[0]); _scissor->height() = SGMiscf::roundToInt(height);
if( canvas ) getOrCreateStateSet()->setAttributeAndModes(_scissor);
// Canvas has y axis upside down
scissor->y() = canvas->getSizeY() - scissor->y() - scissor->height();
getOrCreateStateSet()->setAttributeAndModes(scissor); SGPropertyNode* clip_frame = _node->getChild("clip-frame", 0);
if( clip_frame )
valueChanged(clip_frame);
}
//----------------------------------------------------------------------------
void Element::setClipFrame(ReferenceFrame rf)
{
if( _scissor )
{
_scissor->_coord_reference = rf;
_attributes_dirty |= SCISSOR_COORDS;
}
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -481,11 +520,78 @@ namespace canvas
osg::BoundingBox transformed; osg::BoundingBox transformed;
const osg::BoundingBox& bb = _drawable->getBound(); const osg::BoundingBox& bb = _drawable->getBound();
for(int i = 0; i < 4; ++i) for(int i = 0; i < 4; ++i)
transformed.expandBy( m * bb.corner(i) ); transformed.expandBy( bb.corner(i) * m );
return transformed; return transformed;
} }
//----------------------------------------------------------------------------
osg::Matrix Element::getMatrix() const
{
if( !(_attributes_dirty & TRANSFORM) )
return _transform->getMatrix();
osg::Matrix m;
for( size_t i = 0; i < _transform_types.size(); ++i )
{
// Skip unused indizes...
if( _transform_types[i] == TT_NONE )
continue;
SGPropertyNode* tf_node = _node->getChild("tf", i, true);
// Build up the matrix representation of the current transform node
osg::Matrix tf;
switch( _transform_types[i] )
{
case TT_MATRIX:
tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
tf_node->getDoubleValue("m[1]", 0),
0,
tf_node->getDoubleValue("m[6]", 0),
tf_node->getDoubleValue("m[2]", 0),
tf_node->getDoubleValue("m[3]", 1),
0,
tf_node->getDoubleValue("m[7]", 0),
0,
0,
1,
0,
tf_node->getDoubleValue("m[4]", 0),
tf_node->getDoubleValue("m[5]", 0),
0,
tf_node->getDoubleValue("m[8]", 1) );
break;
case TT_TRANSLATE:
tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
tf_node->getDoubleValue("t[1]", 0),
0 ) );
break;
case TT_ROTATE:
tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
break;
case TT_SCALE:
{
float sx = tf_node->getDoubleValue("s[0]", 1);
// sy defaults to sx...
tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
break;
}
default:
break;
}
m.postMult( tf );
}
_transform->setMatrix(m);
_attributes_dirty &= ~TRANSFORM;
_attributes_dirty |= SCISSOR_COORDS;
return m;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Element::StyleSetters Element::_style_setters; Element::StyleSetters Element::_style_setters;
@@ -498,11 +604,13 @@ namespace canvas
_canvas( canvas ), _canvas( canvas ),
_parent( parent ), _parent( parent ),
_attributes_dirty( 0 ), _attributes_dirty( 0 ),
_transform_dirty( false ),
_transform( new osg::MatrixTransform ), _transform( new osg::MatrixTransform ),
_style( parent_style ), _style( parent_style ),
_scissor( 0 ),
_drawable( 0 ) _drawable( 0 )
{ {
staticInit();
SG_LOG SG_LOG
( (
SG_GL, SG_GL,
@@ -510,10 +618,93 @@ namespace canvas
"New canvas element " << node->getPath() "New canvas element " << node->getPath()
); );
if( !isInit<Element>() ) // Ensure elements are drawn in order they appear in the element tree
_transform->getOrCreateStateSet()
->setRenderBinDetails
(
0,
"PreOrderBin",
osg::StateSet::OVERRIDE_RENDERBIN_DETAILS
);
}
//----------------------------------------------------------------------------
void Element::staticInit()
{
if( isInit<Element>() )
return;
addStyle("clip", "", &Element::setClip, false);
addStyle("clip-frame", "", &Element::setClipFrame, false);
}
//----------------------------------------------------------------------------
bool Element::isStyleEmpty(const SGPropertyNode* child) const
{
return !child
|| simgear::strutils::strip(child->getStringValue()).empty();
}
//----------------------------------------------------------------------------
bool Element::canApplyStyle(const SGPropertyNode* child) const
{
if( _node == child->getParent() )
return true;
// Parent values do not override if element has own value
return isStyleEmpty( _node->getChild(child->getName()) );
}
//----------------------------------------------------------------------------
bool Element::setStyleImpl( const SGPropertyNode* child,
const StyleInfo* style_info )
{
const StyleSetter* style_setter = style_info
? &style_info->setter
: getStyleSetter(child->getNameString());
while( style_setter )
{ {
addStyle("clip", "", &Element::setClip); if( style_setter->func(*this, child) )
return true;
style_setter = style_setter->next;
} }
return false;
}
//----------------------------------------------------------------------------
const Element::StyleInfo*
Element::getStyleInfo(const std::string& name) const
{
StyleSetters::const_iterator setter = _style_setters.find(name);
if( setter == _style_setters.end() )
return 0;
return &setter->second;
}
//----------------------------------------------------------------------------
const Element::StyleSetter*
Element::getStyleSetter(const std::string& name) const
{
const StyleInfo* info = getStyleInfo(name);
return info ? &info->setter : 0;
}
//----------------------------------------------------------------------------
const SGPropertyNode*
Element::getParentStyle(const SGPropertyNode* child) const
{
// Try to get value from parent...
if( _parent )
{
Style::const_iterator style =
_parent->_style.find(child->getNameString());
if( style != _parent->_style.end() )
return style->second;
}
// ...or reset to default if none is available
return child; // TODO somehow get default value for each style?
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
+132 -26
View File
@@ -23,13 +23,13 @@
#include <simgear/canvas/CanvasEvent.hxx> #include <simgear/canvas/CanvasEvent.hxx>
#include <simgear/props/PropertyBasedElement.hxx> #include <simgear/props/PropertyBasedElement.hxx>
#include <simgear/misc/stdint.hxx> // for uint32_t #include <simgear/misc/stdint.hxx> // for uint32_t
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <osg/BoundingBox> #include <osg/BoundingBox>
#include <osg/MatrixTransform> #include <osg/MatrixTransform>
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <boost/function.hpp> #include <boost/function.hpp>
#include <boost/type_traits/is_base_of.hpp>
namespace osg namespace osg
{ {
@@ -71,6 +71,19 @@ namespace canvas
{ {
StyleSetter setter; ///!< Function(s) for setting this style StyleSetter setter; ///!< Function(s) for setting this style
std::string type; ///!< Interpolation type std::string type; ///!< Interpolation type
bool inheritable; ///!< Whether children can inherit this style from
/// their parents
};
/**
* Coordinate reference frame (eg. "clip" property)
*/
enum ReferenceFrame
{
GLOBAL, ///!< Global coordinates
PARENT, ///!< Coordinates relative to parent coordinate frame
LOCAL ///!< Coordinates relative to local coordinates (parent
/// coordinates with local transformations applied)
}; };
/** /**
@@ -82,6 +95,7 @@ namespace canvas
virtual void onDestroy(); virtual void onDestroy();
ElementWeakPtr getWeakPtr() const; ElementWeakPtr getWeakPtr() const;
ElementPtr getParent();
/** /**
* Called every frame to update internal state * Called every frame to update internal state
@@ -90,7 +104,7 @@ namespace canvas
*/ */
virtual void update(double dt); virtual void update(double dt);
naRef addEventListener(const nasal::CallContext& ctx); bool addEventListener(const std::string& type, const EventListener& cb);
virtual void clearEventListener(); virtual void clearEventListener();
virtual bool accept(EventVisitor& visitor); virtual bool accept(EventVisitor& visitor);
@@ -117,7 +131,8 @@ namespace canvas
SGPropertyNode * child ); SGPropertyNode * child );
virtual void valueChanged(SGPropertyNode * child); virtual void valueChanged(SGPropertyNode * child);
virtual bool setStyle(const SGPropertyNode* child); virtual bool setStyle( const SGPropertyNode* child,
const StyleInfo* style_info = 0 );
/** /**
* Set clipping shape * Set clipping shape
@@ -127,6 +142,11 @@ namespace canvas
*/ */
void setClip(const std::string& clip); void setClip(const std::string& clip);
/**
* Clipping coordinates reference frame
*/
void setClipFrame(ReferenceFrame rf);
/** /**
* Write the given bounding box to the property tree * Write the given bounding box to the property tree
*/ */
@@ -137,6 +157,11 @@ namespace canvas
*/ */
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const; virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
/**
* Get the transformation matrix (product of all transforms)
*/
osg::Matrix getMatrix() const;
/** /**
* Create an canvas Element * Create an canvas Element
* *
@@ -161,8 +186,10 @@ namespace canvas
enum Attributes enum Attributes
{ {
BLEND_FUNC = 0x0001, TRANSFORM = 1,
LAST_ATTRIBUTE = BLEND_FUNC << 1 BLEND_FUNC = TRANSFORM << 1,
SCISSOR_COORDS = BLEND_FUNC << 1,
LAST_ATTRIBUTE = SCISSOR_COORDS << 1
}; };
enum TransformType enum TransformType
@@ -174,19 +201,21 @@ namespace canvas
TT_SCALE TT_SCALE
}; };
class RelativeScissor;
CanvasWeakPtr _canvas; CanvasWeakPtr _canvas;
Element *_parent; Element *_parent;
uint32_t _attributes_dirty; mutable uint32_t _attributes_dirty;
bool _transform_dirty;
osg::observer_ptr<osg::MatrixTransform> _transform; osg::observer_ptr<osg::MatrixTransform> _transform;
std::vector<TransformType> _transform_types; std::vector<TransformType> _transform_types;
Style _style; Style _style;
std::vector<SGPropertyNode_ptr> _bounding_box; std::vector<SGPropertyNode_ptr> _bounding_box;
RelativeScissor *_scissor;
typedef std::vector<EventListenerPtr> Listener; typedef std::vector<EventListener> Listener;
typedef std::map<Event::Type, Listener> ListenerMap; typedef std::map<Event::Type, Listener> ListenerMap;
ListenerMap _listener; ListenerMap _listener;
@@ -194,6 +223,8 @@ namespace canvas
typedef std::map<std::string, StyleInfo> StyleSetters; typedef std::map<std::string, StyleInfo> StyleSetters;
static StyleSetters _style_setters; static StyleSetters _style_setters;
static void staticInit();
Element( const CanvasWeakPtr& canvas, Element( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
const Style& parent_style, const Style& parent_style,
@@ -207,7 +238,7 @@ namespace canvas
* @tparam Derived (Derived) class type * @tparam Derived (Derived) class type
*/ */
template<class Derived> template<class Derived>
bool isInit() const static bool isInit()
{ {
static bool is_init = false; static bool is_init = false;
if( is_init ) if( is_init )
@@ -236,10 +267,12 @@ namespace canvas
typename T2, typename T2,
class Derived class Derived
> >
static
StyleSetter StyleSetter
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
const boost::function<void (Derived&, T2)>& setter ) const boost::function<void (Derived&, T2)>& setter,
bool inheritable = true )
{ {
StyleInfo& style_info = _style_setters[ name ]; StyleInfo& style_info = _style_setters[ name ];
if( !type.empty() ) if( !type.empty() )
@@ -255,6 +288,8 @@ namespace canvas
style_info.type = type; style_info.type = type;
} }
// TODO check if changed?
style_info.inheritable = inheritable;
StyleSetter* style = &style_info.setter; StyleSetter* style = &style_info.setter;
while( style->next ) while( style->next )
@@ -276,28 +311,33 @@ namespace canvas
typename T, typename T,
class Derived class Derived
> >
static
StyleSetter StyleSetter
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
const boost::function<void (Derived&, T)>& setter ) const boost::function<void (Derived&, T)>& setter,
bool inheritable = true )
{ {
return addStyle<T, T>(name, type, setter); return addStyle<T, T>(name, type, setter, inheritable);
} }
template< template<
typename T, typename T,
class Derived class Derived
> >
static
StyleSetter StyleSetter
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
void (Derived::*setter)(T) ) void (Derived::*setter)(T),
bool inheritable = true )
{ {
return addStyle<T, T> return addStyle<T, T>
( (
name, name,
type, type,
boost::function<void (Derived&, T)>(setter) boost::function<void (Derived&, T)>(setter),
inheritable
); );
} }
@@ -306,32 +346,38 @@ namespace canvas
typename T2, typename T2,
class Derived class Derived
> >
static
StyleSetterFunc StyleSetterFunc
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
void (Derived::*setter)(T2) ) void (Derived::*setter)(T2),
bool inheritable = true )
{ {
return addStyle<T1> return addStyle<T1>
( (
name, name,
type, type,
boost::function<void (Derived&, T2)>(setter) boost::function<void (Derived&, T2)>(setter),
inheritable
); );
} }
template< template<
class Derived class Derived
> >
static
StyleSetter StyleSetter
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
void (Derived::*setter)(const std::string&) ) void (Derived::*setter)(const std::string&),
bool inheritable = true )
{ {
return addStyle<const char*, const std::string&> return addStyle<const char*, const std::string&>
( (
name, name,
type, type,
boost::function<void (Derived&, const std::string&)>(setter) boost::function<void (Derived&, const std::string&)>(setter),
inheritable
); );
} }
@@ -341,13 +387,21 @@ namespace canvas
class Other, class Other,
class OtherRef class OtherRef
> >
static
StyleSetter StyleSetter
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
void (Other::*setter)(T), void (Other::*setter)(T),
OtherRef Derived::*instance_ref ) OtherRef Derived::*instance_ref,
bool inheritable = true )
{ {
return addStyle<T, T>(name, type, bindOther(setter, instance_ref)); return addStyle<T, T>
(
name,
type,
bindOther(setter, instance_ref),
inheritable
);
} }
template< template<
@@ -357,13 +411,21 @@ namespace canvas
class Other, class Other,
class OtherRef class OtherRef
> >
static
StyleSetter StyleSetter
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
void (Other::*setter)(T2), void (Other::*setter)(T2),
OtherRef Derived::*instance_ref ) OtherRef Derived::*instance_ref,
bool inheritable = true )
{ {
return addStyle<T1>(name, type, bindOther(setter, instance_ref)); return addStyle<T1>
(
name,
type,
bindOther(setter, instance_ref),
inheritable
);
} }
template< template<
@@ -373,13 +435,21 @@ namespace canvas
class Other, class Other,
class OtherRef class OtherRef
> >
static
StyleSetter StyleSetter
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
const boost::function<void (Other&, T2)>& setter, const boost::function<void (Other&, T2)>& setter,
OtherRef Derived::*instance_ref ) OtherRef Derived::*instance_ref,
bool inheritable = true )
{ {
return addStyle<T1>(name, type, bindOther(setter, instance_ref)); return addStyle<T1>
(
name,
type,
bindOther(setter, instance_ref),
inheritable
);
} }
template< template<
@@ -387,22 +457,26 @@ namespace canvas
class Other, class Other,
class OtherRef class OtherRef
> >
static
StyleSetter StyleSetter
addStyle( const std::string& name, addStyle( const std::string& name,
const std::string& type, const std::string& type,
void (Other::*setter)(const std::string&), void (Other::*setter)(const std::string&),
OtherRef Derived::*instance_ref ) OtherRef Derived::*instance_ref,
bool inheritable = true )
{ {
return addStyle<const char*, const std::string&> return addStyle<const char*, const std::string&>
( (
name, name,
type, type,
boost::function<void (Other&, const std::string&)>(setter), boost::function<void (Other&, const std::string&)>(setter),
instance_ref instance_ref,
inheritable
); );
} }
template<typename T, class Derived, class Other, class OtherRef> template<typename T, class Derived, class Other, class OtherRef>
static
boost::function<void (Derived&, T)> boost::function<void (Derived&, T)>
bindOther( void (Other::*setter)(T), OtherRef Derived::*instance_ref ) bindOther( void (Other::*setter)(T), OtherRef Derived::*instance_ref )
{ {
@@ -410,6 +484,7 @@ namespace canvas
} }
template<typename T, class Derived, class Other, class OtherRef> template<typename T, class Derived, class Other, class OtherRef>
static
boost::function<void (Derived&, T)> boost::function<void (Derived&, T)>
bindOther( const boost::function<void (Other&, T)>& setter, bindOther( const boost::function<void (Other&, T)>& setter,
OtherRef Derived::*instance_ref ) OtherRef Derived::*instance_ref )
@@ -427,6 +502,7 @@ namespace canvas
} }
template<typename T1, typename T2, class Derived> template<typename T1, typename T2, class Derived>
static
StyleSetterFuncUnchecked StyleSetterFuncUnchecked
bindStyleSetter( const std::string& name, bindStyleSetter( const std::string& name,
const boost::function<void (Derived&, T2)>& setter ) const boost::function<void (Derived&, T2)>& setter )
@@ -441,6 +517,15 @@ namespace canvas
); );
} }
bool isStyleEmpty(const SGPropertyNode* child) const;
bool canApplyStyle(const SGPropertyNode* child) const;
bool setStyleImpl( const SGPropertyNode* child,
const StyleInfo* style_info = 0 );
const StyleInfo* getStyleInfo(const std::string& name) const;
const StyleSetter* getStyleSetter(const std::string& name) const;
const SGPropertyNode* getParentStyle(const SGPropertyNode* child) const;
virtual void childAdded(SGPropertyNode * child) {} virtual void childAdded(SGPropertyNode * child) {}
virtual void childRemoved(SGPropertyNode * child){} virtual void childRemoved(SGPropertyNode * child){}
virtual void childChanged(SGPropertyNode * child){} virtual void childChanged(SGPropertyNode * child){}
@@ -495,6 +580,27 @@ namespace canvas
}; };
} // namespace canvas } // namespace canvas
template<>
struct enum_traits<canvas::Element::ReferenceFrame>
{
static const char* name()
{
return "canvas::Element::ReferenceFrame";
}
static canvas::Element::ReferenceFrame defVal()
{
return canvas::Element::GLOBAL;
}
static bool validate(int frame)
{
return frame >= canvas::Element::GLOBAL
&& frame <= canvas::Element::LOCAL;
}
};
} // namespace simgear } // namespace simgear
#endif /* CANVAS_ELEMENT_HXX_ */ #endif /* CANVAS_ELEMENT_HXX_ */
+61 -29
View File
@@ -38,6 +38,7 @@ namespace canvas
template<typename ElementType> template<typename ElementType>
void add(ElementFactories& factories) void add(ElementFactories& factories)
{ {
ElementType::staticInit();
factories[ElementType::TYPE_NAME] = &Element::create<ElementType>; factories[ElementType::TYPE_NAME] = &Element::create<ElementType>;
} }
@@ -45,6 +46,26 @@ namespace canvas
ElementFactories Group::_child_factories; ElementFactories Group::_child_factories;
const std::string Group::TYPE_NAME = "group"; const std::string Group::TYPE_NAME = "group";
void warnTransformExpired(const char* member_name)
{
SG_LOG( SG_GENERAL,
SG_WARN,
"canvas::Group::" << member_name << ": Group has expired." );
}
//----------------------------------------------------------------------------
void Group::staticInit()
{
if( isInit<Group>() )
return;
add<Group>(_child_factories);
add<Image>(_child_factories);
add<Map >(_child_factories);
add<Path >(_child_factories);
add<Text >(_child_factories);
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Group::Group( const CanvasWeakPtr& canvas, Group::Group( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
@@ -52,14 +73,7 @@ namespace canvas
Element* parent ): Element* parent ):
Element(canvas, node, parent_style, parent) Element(canvas, node, parent_style, parent)
{ {
if( !isInit<Group>() ) staticInit();
{
add<Group>(_child_factories);
add<Image>(_child_factories);
add<Map >(_child_factories);
add<Path >(_child_factories);
add<Text >(_child_factories);
}
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -119,8 +133,13 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
ElementPtr Group::getElementById(const std::string& id) ElementPtr Group::getElementById(const std::string& id)
{ {
std::vector<GroupPtr> groups; if( !_transform.valid() )
{
warnTransformExpired("getElementById");
return ElementPtr();
}
std::vector<GroupPtr> groups;
for(size_t i = 0; i < _transform->getNumChildren(); ++i) for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{ {
const ElementPtr& el = getChildByIndex(i); const ElementPtr& el = getChildByIndex(i);
@@ -145,6 +164,8 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Group::clearEventListener() void Group::clearEventListener()
{ {
if( !_transform.valid() )
return warnTransformExpired("clearEventListener");
for(size_t i = 0; i < _transform->getNumChildren(); ++i) for(size_t i = 0; i < _transform->getNumChildren(); ++i)
getChildByIndex(i)->clearEventListener(); getChildByIndex(i)->clearEventListener();
@@ -174,21 +195,23 @@ namespace canvas
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
bool Group::setStyle(const SGPropertyNode* style) bool Group::setStyle( const SGPropertyNode* style,
const StyleInfo* style_info )
{ {
// Don't propagate styles directly applicable to this group if( !canApplyStyle(style) )
if( Element::setStyle(style) )
return true;
if( style->getParent() != _node
&& _style.find(style->getNameString()) != _style.end() )
return false; return false;
bool handled = false; bool handled = setStyleImpl(style, style_info);
for(size_t i = 0; i < _transform->getNumChildren(); ++i) if( style_info->inheritable )
{ {
if( getChildByIndex(i)->setStyle(style) ) if( !_transform.valid() )
handled = true; {
warnTransformExpired("setStyle");
return false;
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
handled |= getChildByIndex(i)->setStyle(style, style_info);
} }
return handled; return handled;
@@ -198,6 +221,11 @@ namespace canvas
osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const
{ {
osg::BoundingBox bb; osg::BoundingBox bb;
if( !_transform.valid() )
{
warnTransformExpired("getTransformedBounds");
return bb;
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i) for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{ {
@@ -236,6 +264,9 @@ namespace canvas
ElementFactory child_factory = getChildFactory( child->getNameString() ); ElementFactory child_factory = getChildFactory( child->getNameString() );
if( child_factory ) if( child_factory )
{ {
if( !_transform.valid() )
return warnTransformExpired("childAdded");
ElementPtr element = child_factory(_canvas, child, _style, this); ElementPtr element = child_factory(_canvas, child, _style, this);
// Add to osg scene graph... // Add to osg scene graph...
@@ -247,12 +278,9 @@ namespace canvas
return; return;
} }
if( !Element::setStyle(child) ) StyleInfo const* style = getStyleInfo(child->getNameString());
{ if( style && style->inheritable )
// Only add style if not applicable to group itself
_style[ child->getNameString() ] = child; _style[ child->getNameString() ] = child;
setStyle(child);
}
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -285,9 +313,7 @@ namespace canvas
} }
else else
{ {
Style::iterator style = _style.find(node->getNameString()); _style.erase( node->getNameString() );
if( style != _style.end() )
_style.erase(style);
} }
} }
@@ -303,7 +329,7 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Group::handleZIndexChanged(ElementPtr child, int z_index) void Group::handleZIndexChanged(ElementPtr child, int z_index)
{ {
if( !child ) if( !child || !_transform.valid() )
return; return;
osg::ref_ptr<osg::MatrixTransform> tf = child->getMatrixTransform(); osg::ref_ptr<osg::MatrixTransform> tf = child->getMatrixTransform();
@@ -364,6 +390,12 @@ namespace canvas
ElementPtr Group::findChild( const SGPropertyNode* node, ElementPtr Group::findChild( const SGPropertyNode* node,
const std::string& id ) const const std::string& id ) const
{ {
if( !_transform.valid() )
{
warnTransformExpired("findChild");
return ElementPtr();
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i) for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{ {
ElementPtr el = getChildByIndex(i); ElementPtr el = getChildByIndex(i);
+3 -1
View File
@@ -35,6 +35,7 @@ namespace canvas
{ {
public: public:
static const std::string TYPE_NAME; static const std::string TYPE_NAME;
static void staticInit();
typedef std::list< std::pair< const SGPropertyNode*, typedef std::list< std::pair< const SGPropertyNode*,
ElementPtr ElementPtr
@@ -92,7 +93,8 @@ namespace canvas
virtual bool traverse(EventVisitor& visitor); virtual bool traverse(EventVisitor& visitor);
virtual bool setStyle(const SGPropertyNode* child); virtual bool setStyle( const SGPropertyNode* child,
const StyleInfo* style_info = 0 );
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const; virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
+18 -12
View File
@@ -89,6 +89,18 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
const std::string Image::TYPE_NAME = "image"; const std::string Image::TYPE_NAME = "image";
//----------------------------------------------------------------------------
void Image::staticInit()
{
if( isInit<Image>() )
return;
addStyle("fill", "color", &Image::setFill);
addStyle("slice", "", &Image::setSlice);
addStyle("slice-width", "", &Image::setSliceWidth);
addStyle("outset", "", &Image::setOutset);
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Image::Image( const CanvasWeakPtr& canvas, Image::Image( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
@@ -100,6 +112,8 @@ namespace canvas
_src_rect(0,0), _src_rect(0,0),
_region(0,0) _region(0,0)
{ {
staticInit();
_geom = new osg::Geometry; _geom = new osg::Geometry;
_geom->setUseDisplayList(false); _geom->setUseDisplayList(false);
@@ -128,16 +142,7 @@ namespace canvas
setDrawable(_geom); setDrawable(_geom);
if( !isInit<Image>() )
{
addStyle("fill", "color", &Image::setFill);
addStyle("slice", "", &Image::setSlice);
addStyle("slice-width", "", &Image::setSliceWidth);
addStyle("outset", "", &Image::setOutset);
}
setFill("#ffffff"); // TODO how should we handle default values? setFill("#ffffff"); // TODO how should we handle default values?
setupStyle(); setupStyle();
} }
@@ -400,8 +405,9 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Image::setFill(const std::string& fill) void Image::setFill(const std::string& fill)
{ {
osg::Vec4 color; osg::Vec4 color(1,1,1,1);
if( !parseColor(fill, color) ) if( !fill.empty() // If no color is given default to white
&& !parseColor(fill, color) )
return; return;
_colors->front() = color; _colors->front() = color;
@@ -559,7 +565,7 @@ namespace canvas
} }
else else
{ {
setImage( canvas->getSystemAdapter()->getImage(path) ); setImage( Canvas::getSystemAdapter()->getImage(path) );
} }
} }
} }
+1
View File
@@ -35,6 +35,7 @@ namespace canvas
{ {
public: public:
static const std::string TYPE_NAME; static const std::string TYPE_NAME;
static void staticInit();
/** /**
* @param node Property node containing settings for this image: * @param node Property node containing settings for this image:
+14 -1
View File
@@ -46,6 +46,17 @@ namespace canvas
const std::string GEO = "-geo"; const std::string GEO = "-geo";
const std::string Map::TYPE_NAME = "map"; const std::string Map::TYPE_NAME = "map";
//----------------------------------------------------------------------------
void Map::staticInit()
{
Group::staticInit();
if( isInit<Map>() )
return;
// Do some initialization if needed...
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Map::Map( const CanvasWeakPtr& canvas, Map::Map( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
@@ -56,7 +67,7 @@ namespace canvas
_projection(new SansonFlamsteedProjection), _projection(new SansonFlamsteedProjection),
_projection_dirty(true) _projection_dirty(true)
{ {
staticInit();
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -194,6 +205,8 @@ namespace canvas
_projection->setOrientation(child->getFloatValue()); _projection->setOrientation(child->getFloatValue());
else if( child->getNameString() == "range" ) else if( child->getNameString() == "range" )
_projection->setRange(child->getDoubleValue()); _projection->setRange(child->getDoubleValue());
else if( child->getNameString() == "screen-range" )
_projection->setScreenRange(child->getDoubleValue());
else else
return Group::childChanged(child); return Group::childChanged(child);
+1
View File
@@ -36,6 +36,7 @@ namespace canvas
{ {
public: public:
static const std::string TYPE_NAME; static const std::string TYPE_NAME;
static void staticInit();
Map( const CanvasWeakPtr& canvas, Map( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
+28 -16
View File
@@ -110,7 +110,7 @@ namespace canvas
*/ */
void setFill(const std::string& fill) void setFill(const std::string& fill)
{ {
if( fill == "none" ) if( fill.empty() || fill == "none" )
{ {
_mode &= ~VG_FILL_PATH; _mode &= ~VG_FILL_PATH;
} }
@@ -150,7 +150,7 @@ namespace canvas
*/ */
void setStroke(const std::string& stroke) void setStroke(const std::string& stroke)
{ {
if( stroke == "none" ) if( stroke.empty() || stroke == "none" )
{ {
_mode &= ~VG_STROKE_PATH; _mode &= ~VG_STROKE_PATH;
} }
@@ -277,7 +277,8 @@ namespace canvas
{ {
osg::BoundingBox bb; osg::BoundingBox bb;
osg::Vec2f cur; // VG "Current point" (in local coordinates) osg::Vec2f cur(0, 0), // VG "Current point" (in local coordinates)
sub(0, 0); // beginning of current sub path
VGubyte cmd_index = 0; VGubyte cmd_index = 0;
for( size_t i = 0, ci = 0; for( size_t i = 0, ci = 0;
i < _cmds.size() && ci < _coords.size(); i < _cmds.size() && ci < _coords.size();
@@ -297,6 +298,7 @@ namespace canvas
switch( cmd ) switch( cmd )
{ {
case VG_CLOSE_PATH: case VG_CLOSE_PATH:
cur = sub;
break; break;
case VG_MOVE_TO: case VG_MOVE_TO:
case VG_LINE_TO: case VG_LINE_TO:
@@ -352,7 +354,12 @@ namespace canvas
} }
if( num_coords > 0 ) if( num_coords > 0 )
{
cur = points[ num_coords - 1 ]; cur = points[ num_coords - 1 ];
if( cmd == VG_MOVE_TO )
sub = cur;
}
} }
return bb; return bb;
@@ -472,6 +479,22 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
const std::string Path::TYPE_NAME = "path"; const std::string Path::TYPE_NAME = "path";
//----------------------------------------------------------------------------
void Path::staticInit()
{
if( isInit<Path>() )
return;
PathDrawableRef Path::*path = &Path::_path;
addStyle("fill", "color", &PathDrawable::setFill, path);
addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
addStyle("stroke", "color", &PathDrawable::setStroke, path);
addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Path::Path( const CanvasWeakPtr& canvas, Path::Path( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
@@ -480,20 +503,9 @@ namespace canvas
Element(canvas, node, parent_style, parent), Element(canvas, node, parent_style, parent),
_path( new PathDrawable(this) ) _path( new PathDrawable(this) )
{ {
staticInit();
setDrawable(_path); setDrawable(_path);
if( !isInit<Path>() )
{
PathDrawableRef Path::*path = &Path::_path;
addStyle("fill", "color", &PathDrawable::setFill, path);
addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
addStyle("stroke", "color", &PathDrawable::setStroke, path);
addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
}
setupStyle(); setupStyle();
} }
+1
View File
@@ -31,6 +31,7 @@ namespace canvas
{ {
public: public:
static const std::string TYPE_NAME; static const std::string TYPE_NAME;
static void staticInit();
Path( const CanvasWeakPtr& canvas, Path( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
+46 -32
View File
@@ -20,7 +20,7 @@
#include <simgear/canvas/Canvas.hxx> #include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasSystemAdapter.hxx> #include <simgear/canvas/CanvasSystemAdapter.hxx>
#include <simgear/scene/util/parse_color.hxx> #include <simgear/scene/util/parse_color.hxx>
#include <simgear/structure/OSGVersion.hxx> #include <osg/Version>
#include <osgText/Text> #include <osgText/Text>
namespace simgear namespace simgear
@@ -36,6 +36,7 @@ namespace canvas
void setFontResolution(int res); void setFontResolution(int res);
void setCharacterAspect(float aspect); void setCharacterAspect(float aspect);
void setLineHeight(float factor);
void setFill(const std::string& fill); void setFill(const std::string& fill);
void setBackgroundColor(const std::string& fill); void setBackgroundColor(const std::string& fill);
@@ -54,7 +55,7 @@ namespace canvas
Text::TextOSG::TextOSG(canvas::Text* text): Text::TextOSG::TextOSG(canvas::Text* text):
_text_element(text) _text_element(text)
{ {
setBackdropImplementation(NO_DEPTH_BUFFER);
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -69,6 +70,12 @@ namespace canvas
setCharacterSize(getCharacterHeight(), aspect); setCharacterSize(getCharacterHeight(), aspect);
} }
//----------------------------------------------------------------------------
void Text::TextOSG::setLineHeight(float factor)
{
setLineSpacing(factor - 1);
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Text::TextOSG::setFill(const std::string& fill) void Text::TextOSG::setFill(const std::string& fill)
{ {
@@ -175,7 +182,7 @@ namespace canvas
if( !bb.valid() ) if( !bb.valid() )
return bb; return bb;
#if SG_OSG_VERSION_LESS_THAN(3,1,0) #if OSG_VERSION_LESS_THAN(3,1,0)
// TODO bounding box still doesn't seem always right (eg. with center // TODO bounding box still doesn't seem always right (eg. with center
// horizontal alignment not completely accurate) // horizontal alignment not completely accurate)
bb._min.y() += _offset.y(); bb._min.y() += _offset.y();
@@ -254,6 +261,39 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
const std::string Text::TYPE_NAME = "text"; const std::string Text::TYPE_NAME = "text";
//----------------------------------------------------------------------------
void Text::staticInit()
{
if( isInit<Text>() )
return;
osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
addStyle("fill", "color", &TextOSG::setFill, text);
addStyle("background", "color", &TextOSG::setBackgroundColor, text);
addStyle("character-size",
"numeric",
static_cast<
void (TextOSG::*)(float)
> (&TextOSG::setCharacterSize),
text);
addStyle("character-aspect-ratio",
"numeric",
&TextOSG::setCharacterAspect, text);
addStyle("line-height", "numeric", &TextOSG::setLineHeight, text);
addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
// TEXT = 1 default
// BOUNDINGBOX = 2
// FILLEDBOUNDINGBOX = 4
// ALIGNMENT = 8
addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
addStyle("font", "", &Text::setFont);
addStyle("alignment", "", &Text::setAlignment);
addStyle("text", "", &Text::setText, false);
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
Text::Text( const CanvasWeakPtr& canvas, Text::Text( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
@@ -262,39 +302,13 @@ namespace canvas
Element(canvas, node, parent_style, parent), Element(canvas, node, parent_style, parent),
_text( new Text::TextOSG(this) ) _text( new Text::TextOSG(this) )
{ {
staticInit();
setDrawable(_text); setDrawable(_text);
_text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS); _text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
_text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION); _text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
_text->setRotation(osg::Quat(osg::PI, osg::X_AXIS)); _text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
if( !isInit<Text>() )
{
osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
addStyle("fill", "color", &TextOSG::setFill, text);
addStyle("background", "color", &TextOSG::setBackgroundColor, text);
addStyle("character-size",
"numeric",
static_cast<
void (TextOSG::*)(float)
> (&TextOSG::setCharacterSize),
text);
addStyle("character-aspect-ratio",
"numeric",
&TextOSG::setCharacterAspect, text);
addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
// TEXT = 1 default
// BOUNDINGBOX = 2
// FILLEDBOUNDINGBOX = 4
// ALIGNMENT = 8
addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
addStyle("font", "", &Text::setFont);
addStyle("alignment", "", &Text::setAlignment);
addStyle("text", "", &Text::setText);
}
setupStyle(); setupStyle();
} }
@@ -313,7 +327,7 @@ namespace canvas
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void Text::setFont(const char* name) void Text::setFont(const char* name)
{ {
_text->setFont( _canvas.lock()->getSystemAdapter()->getFont(name) ); _text->setFont( Canvas::getSystemAdapter()->getFont(name) );
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
+1
View File
@@ -34,6 +34,7 @@ namespace canvas
{ {
public: public:
static const std::string TYPE_NAME; static const std::string TYPE_NAME;
static void staticInit();
Text( const CanvasWeakPtr& canvas, Text( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node, const SGPropertyNode_ptr& node,
+1 -1
View File
@@ -46,7 +46,7 @@
# warning GCC compilers prior to 3.4 are suspect # warning GCC compilers prior to 3.4 are suspect
# endif # endif
# define GCC_VERSION (__GNUC__ * 10000 \ # define SG_GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \ + __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__) + __GNUC_PATCHLEVEL__)
# define SG_COMPILER_STR "GNU C++ version " SG_STRINGIZE(__GNUC__) "." SG_STRINGIZE(__GNUC_MINOR__) # define SG_COMPILER_STR "GNU C++ version " SG_STRINGIZE(__GNUC__) "." SG_STRINGIZE(__GNUC_MINOR__)
+2 -5
View File
@@ -34,18 +34,15 @@ class BufferedLogCallback::BufferedLogCallbackPrivate
{ {
public: public:
SGMutex m_mutex; SGMutex m_mutex;
sgDebugClass m_class;
sgDebugPriority m_priority;
vector_cstring m_buffer; vector_cstring m_buffer;
unsigned int m_stamp; unsigned int m_stamp;
unsigned int m_maxLength; unsigned int m_maxLength;
}; };
BufferedLogCallback::BufferedLogCallback(sgDebugClass c, sgDebugPriority p) : BufferedLogCallback::BufferedLogCallback(sgDebugClass c, sgDebugPriority p) :
simgear::LogCallback(c,p),
d(new BufferedLogCallbackPrivate) d(new BufferedLogCallbackPrivate)
{ {
d->m_class = c;
d->m_priority = p;
d->m_stamp = 0; d->m_stamp = 0;
d->m_maxLength = 0xffff; d->m_maxLength = 0xffff;
} }
@@ -63,7 +60,7 @@ void BufferedLogCallback::operator()(sgDebugClass c, sgDebugPriority p,
SG_UNUSED(file); SG_UNUSED(file);
SG_UNUSED(line); SG_UNUSED(line);
if ((c & d->m_class) == 0 || p < d->m_priority) return; if (!shouldLog(c, p)) return;
vector_cstring::value_type msg; vector_cstring::value_type msg;
if (aMessage.size() >= d->m_maxLength) { if (aMessage.size() >= d->m_maxLength) {
+109 -29
View File
@@ -20,10 +20,13 @@
// //
// $Id$ // $Id$
#include <simgear_config.h>
#include "logstream.hxx" #include "logstream.hxx"
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <sstream>
#include <algorithm> #include <algorithm>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
@@ -35,8 +38,8 @@
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#ifdef _WIN32 #ifdef SG_WINDOWS
// for AllocConsole // for AllocConsole, OutputDebugString
#include "windows.h" #include "windows.h"
#endif #endif
@@ -73,37 +76,57 @@ const char* debugClassToString(sgDebugClass c)
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
namespace simgear
{
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
m_class(c),
m_priority(p)
{
}
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
{
return ((c & m_class) != 0 && p >= m_priority);
}
void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
{
m_priority = p;
m_class = c;
}
} // of namespace simgear
//////////////////////////////////////////////////////////////////////////////
class FileLogCallback : public simgear::LogCallback class FileLogCallback : public simgear::LogCallback
{ {
public: public:
FileLogCallback(const std::string& aPath, sgDebugClass c, sgDebugPriority p) : FileLogCallback(const std::string& aPath, sgDebugClass c, sgDebugPriority p) :
m_file(aPath.c_str(), std::ios_base::out | std::ios_base::trunc), simgear::LogCallback(c, p),
m_class(c), m_file(aPath.c_str(), std::ios_base::out | std::ios_base::trunc)
m_priority(p)
{ {
} }
virtual void operator()(sgDebugClass c, sgDebugPriority p, virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& message) const char* file, int line, const std::string& message)
{ {
if ((c & m_class) == 0 || p < m_priority) return; if (!shouldLog(c, p)) return;
m_file << debugClassToString(c) << ":" << (int) p m_file << debugClassToString(c) << ":" << (int) p
<< ":" << file << ":" << line << ":" << message << std::endl; << ":" << file << ":" << line << ":" << message << std::endl;
} }
private: private:
std::ofstream m_file; std::ofstream m_file;
sgDebugClass m_class;
sgDebugPriority m_priority;
}; };
class StderrLogCallback : public simgear::LogCallback class StderrLogCallback : public simgear::LogCallback
{ {
public: public:
StderrLogCallback(sgDebugClass c, sgDebugPriority p) : StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
m_class(c), simgear::LogCallback(c, p)
m_priority(p)
{ {
#ifdef _WIN32 #ifdef SG_WINDOWS
AllocConsole(); // but only if we want a console AllocConsole(); // but only if we want a console
freopen("conin$", "r", stdin); freopen("conin$", "r", stdin);
freopen("conout$", "w", stdout); freopen("conout$", "w", stdout);
@@ -111,29 +134,42 @@ public:
#endif #endif
} }
void setLogLevels( sgDebugClass c, sgDebugPriority p )
{
m_priority = p;
m_class = c;
}
virtual void operator()(sgDebugClass c, sgDebugPriority p, virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage) const char* file, int line, const std::string& aMessage)
{ {
if ((c & m_class) == 0 || p < m_priority) return; if (!shouldLog(c, p)) return;
// if running under MSVC, we could use OutputDebugString here
fprintf(stderr, "%s\n", aMessage.c_str()); fprintf(stderr, "%s\n", aMessage.c_str());
//fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p, //fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p,
// file, line, aMessage.c_str()); // file, line, aMessage.c_str());
fflush(stderr); fflush(stderr);
} }
private:
sgDebugClass m_class;
sgDebugPriority m_priority;
}; };
#ifdef SG_WINDOWS
class WinDebugLogCallback : public simgear::LogCallback
{
public:
WinDebugLogCallback(sgDebugClass c, sgDebugPriority p) :
simgear::LogCallback(c, p)
{
}
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage)
{
if (!shouldLog(c, p)) return;
std::ostringstream os;
os << debugClassToString(c) << ":" << aMessage << std::endl;
OutputDebugStringA(os.str().c_str());
}
};
#endif
class LogStreamPrivate : public SGThread class LogStreamPrivate : public SGThread
{ {
private: private:
@@ -181,10 +217,20 @@ public:
LogStreamPrivate() : LogStreamPrivate() :
m_logClass(SG_ALL), m_logClass(SG_ALL),
m_logPriority(SG_ALERT), m_logPriority(SG_ALERT),
m_isRunning(false) m_isRunning(false),
m_consoleRequested(false)
{ {
m_stderrCallback = new StderrLogCallback(m_logClass, m_logPriority);
addCallback(m_stderrCallback); #if !defined(SG_WINDOWS)
m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
m_consoleCallbacks.push_back(m_callbacks.back());
m_consoleRequested = true;
#endif
#if defined (SG_WINDOWS) && !defined(NDEBUG)
m_callbacks.push_back(new WinDebugLogCallback(m_logClass, m_logPriority));
m_consoleCallbacks.push_back(m_callbacks.back());
#endif
} }
SGMutex m_lock; SGMutex m_lock;
@@ -192,11 +238,14 @@ public:
typedef std::vector<simgear::LogCallback*> CallbackVec; typedef std::vector<simgear::LogCallback*> CallbackVec;
CallbackVec m_callbacks; CallbackVec m_callbacks;
/// subset of callbacks which correspond to stdout / console,
/// and hence should dynamically reflect console logging settings
CallbackVec m_consoleCallbacks;
sgDebugClass m_logClass; sgDebugClass m_logClass;
sgDebugPriority m_logPriority; sgDebugPriority m_logPriority;
bool m_isRunning; bool m_isRunning;
StderrLogCallback* m_stderrCallback; bool m_consoleRequested;
void startLog() void startLog()
{ {
@@ -260,7 +309,9 @@ public:
PauseThread pause(this); PauseThread pause(this);
m_logPriority = p; m_logPriority = p;
m_logClass = c; m_logClass = c;
m_stderrCallback->setLogLevels(c, p); BOOST_FOREACH(simgear::LogCallback* cb, m_consoleCallbacks) {
cb->setLogLevels(c, p);
}
} }
bool would_log( sgDebugClass c, sgDebugPriority p ) const bool would_log( sgDebugClass c, sgDebugPriority p ) const
@@ -275,6 +326,18 @@ public:
LogEntry entry(c, p, fileName, line, msg); LogEntry entry(c, p, fileName, line, msg);
m_entries.push(entry); m_entries.push(entry);
} }
void requestConsole()
{
PauseThread pause(this);
if (m_consoleRequested) {
return;
}
m_consoleRequested = true;
m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
m_consoleCallbacks.push_back(m_callbacks.back());
}
}; };
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@@ -348,6 +411,13 @@ sglog()
{ {
// Force initialization of cerr. // Force initialization of cerr.
static std::ios_base::Init initializer; static std::ios_base::Init initializer;
// http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
// in the absence of portable memory barrier ops in Simgear,
// let's keep this correct & safe
static SGMutex m;
SGGuard<SGMutex> g(m);
if( !global_logstream ) if( !global_logstream )
global_logstream = new logstream(); global_logstream = new logstream();
return *global_logstream; return *global_logstream;
@@ -359,3 +429,13 @@ logstream::logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p )
global_privateLogstream->addCallback(new FileLogCallback(aPath.str(), c, p)); global_privateLogstream->addCallback(new FileLogCallback(aPath.str(), c, p));
} }
namespace simgear
{
void requestConsole()
{
sglog(); // force creation
global_privateLogstream->requestConsole();
}
} // of namespace simgear
+18
View File
@@ -42,7 +42,23 @@ public:
virtual ~LogCallback() {} virtual ~LogCallback() {}
virtual void operator()(sgDebugClass c, sgDebugPriority p, virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage) = 0; const char* file, int line, const std::string& aMessage) = 0;
void setLogLevels(sgDebugClass c, sgDebugPriority p);
protected:
LogCallback(sgDebugClass c, sgDebugPriority p);
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
private:
sgDebugClass m_class;
sgDebugPriority m_priority;
}; };
/**
* Helper force a console on platforms where it might optional, when
* we need to show a console. This basically means Windows at the
* moment - on other plaforms it's a no-op
*/
void requestConsole();
} // of namespace simgear } // of namespace simgear
@@ -103,6 +119,8 @@ private:
logstream& sglog(); logstream& sglog();
/** \def SG_LOG(C,P,M) /** \def SG_LOG(C,P,M)
* Log a message. * Log a message.
* @param C debug class * @param C debug class
+1 -1
View File
@@ -629,7 +629,7 @@ bool SGMetar::scanWeather()
weather = pre + weather + post; weather = pre + weather + post;
weather.erase(weather.length() - 1); weather.erase(weather.length() - 1);
_weather.push_back(weather); _weather.push_back(weather);
if( w.phenomena.size() > 0 ) if( ! w.phenomena.empty() )
_weather2.push_back( w ); _weather2.push_back( w );
_grpcount++; _grpcount++;
return true; return true;
-1
View File
@@ -42,7 +42,6 @@ private:
float _rain_intensity; float _rain_intensity;
float _clip_distance; float _clip_distance;
int _wind_dir;
osg::Vec3 _wind_vec; osg::Vec3 _wind_vec;
osg::ref_ptr<osgParticle::PrecipitationEffect> _precipitationEffect; osg::ref_ptr<osgParticle::PrecipitationEffect> _precipitationEffect;
+6
View File
@@ -15,7 +15,10 @@ set(HEADERS
sg_socket.hxx sg_socket.hxx
sg_socket_udp.hxx sg_socket_udp.hxx
HTTPClient.hxx HTTPClient.hxx
HTTPFileRequest.hxx
HTTPMemoryRequest.hxx
HTTPRequest.hxx HTTPRequest.hxx
HTTPContentDecode.hxx
DAVMultiStatus.hxx DAVMultiStatus.hxx
SVNRepository.hxx SVNRepository.hxx
SVNDirectory.hxx SVNDirectory.hxx
@@ -35,7 +38,10 @@ set(SOURCES
sg_socket.cxx sg_socket.cxx
sg_socket_udp.cxx sg_socket_udp.cxx
HTTPClient.cxx HTTPClient.cxx
HTTPFileRequest.cxx
HTTPMemoryRequest.cxx
HTTPRequest.cxx HTTPRequest.cxx
HTTPContentDecode.cxx
DAVMultiStatus.cxx DAVMultiStatus.cxx
SVNRepository.cxx SVNRepository.cxx
SVNDirectory.cxx SVNDirectory.cxx
+31 -4
View File
@@ -16,6 +16,10 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <simgear_config.h>
#endif
#include "DAVMultiStatus.hxx" #include "DAVMultiStatus.hxx"
#include <iostream> #include <iostream>
@@ -27,10 +31,15 @@
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include "simgear/debug/logstream.hxx" #include "simgear/debug/logstream.hxx"
#include "simgear/xml/xmlparse.h"
#include "simgear/misc/strutils.hxx" #include "simgear/misc/strutils.hxx"
#include "simgear/structure/exception.hxx" #include "simgear/structure/exception.hxx"
#ifdef SYSTEM_EXPAT
# include <expat.h>
#else
# include "sg_expat.h"
#endif
using std::string; using std::string;
using namespace simgear; using namespace simgear;
@@ -58,7 +67,10 @@ DAVResource::DAVResource(const string& href) :
_url(href), _url(href),
_container(NULL) _container(NULL)
{ {
assert(!href.empty()); assert(!href.empty());
if (strutils::ends_with(href, "/")) {
_url = href.substr(0, _url.size() - 1);
}
} }
void DAVResource::setVersionName(const std::string& aVersion) void DAVResource::setVersionName(const std::string& aVersion)
@@ -171,7 +183,8 @@ class DAVMultiStatus::DAVMultiStatusPrivate
{ {
public: public:
DAVMultiStatusPrivate() : DAVMultiStatusPrivate() :
parserInited(false) parserInited(false),
valid(false)
{ {
rootResource = NULL; rootResource = NULL;
} }
@@ -275,6 +288,7 @@ public:
} }
bool parserInited; bool parserInited;
bool valid;
XML_Parser xmlParser; XML_Parser xmlParser;
DAVResource* rootResource; DAVResource* rootResource;
@@ -354,13 +368,21 @@ void DAVMultiStatus::parseXML(const char* data, int size)
XML_ParserFree(_d->xmlParser); XML_ParserFree(_d->xmlParser);
_d->parserInited = false; _d->parserInited = false;
_d->valid = false;
} }
} }
void DAVMultiStatus::finishParse() void DAVMultiStatus::finishParse()
{ {
if (_d->parserInited) { if (_d->parserInited) {
XML_Parse(_d->xmlParser, NULL, 0, true); if (!XML_Parse(_d->xmlParser, NULL, 0, true)) {
SG_LOG(SG_IO, SG_WARN, "DAV parse error:" << XML_ErrorString(XML_GetErrorCode(_d->xmlParser))
<< " at line:" << XML_GetCurrentLineNumber(_d->xmlParser)
<< " column " << XML_GetCurrentColumnNumber(_d->xmlParser));
_d->valid = false;
} else {
_d->valid = true;
}
XML_ParserFree(_d->xmlParser); XML_ParserFree(_d->xmlParser);
} }
@@ -372,4 +394,9 @@ DAVResource* DAVMultiStatus::resource()
return _d->rootResource; return _d->rootResource;
} }
bool DAVMultiStatus::isValid() const
{
return _d->valid;
}
+2
View File
@@ -129,6 +129,8 @@ public:
void finishParse(); void finishParse();
bool isValid() const;
DAVResource* resource(); DAVResource* resource();
class DAVMultiStatusPrivate; class DAVMultiStatusPrivate;
+398 -296
View File
@@ -1,22 +1,47 @@
/**
* \file HTTPClient.cxx - simple HTTP client engine for SimHear
*/
// Written by James Turner
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include "HTTPClient.hxx" #include "HTTPClient.hxx"
#include "HTTPFileRequest.hxx"
#include <sstream> #include <sstream>
#include <cassert> #include <cassert>
#include <cstdlib> // rand()
#include <list> #include <list>
#include <iostream>
#include <errno.h> #include <errno.h>
#include <map>
#include <stdexcept>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/algorithm/string/case_conv.hpp> #include <boost/algorithm/string/case_conv.hpp>
#include <zlib.h>
#include <simgear/io/sg_netChat.hxx> #include <simgear/io/sg_netChat.hxx>
#include <simgear/io/lowlevel.hxx> #include <simgear/io/HTTPContentDecode.hxx>
#include <simgear/misc/strutils.hxx> #include <simgear/misc/strutils.hxx>
#include <simgear/compiler.h> #include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
#include <simgear/timing/timestamp.hxx> #include <simgear/timing/timestamp.hxx>
#include <simgear/structure/exception.hxx>
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
#include "version.h" #include "version.h"
@@ -26,10 +51,6 @@
# endif # endif
#endif #endif
using std::string;
using std::stringstream;
using std::vector;
namespace simgear namespace simgear
{ {
@@ -39,21 +60,31 @@ namespace HTTP
extern const int DEFAULT_HTTP_PORT = 80; extern const int DEFAULT_HTTP_PORT = 80;
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded"; const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
const unsigned int MAX_INFLIGHT_REQUESTS = 32; const unsigned int MAX_INFLIGHT_REQUESTS = 32;
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
const int ZLIB_INFLATE_WINDOW_BITS = -MAX_WBITS; class Connection;
typedef std::multimap<std::string, Connection*> ConnectionDict;
// see http://www.ietf.org/rfc/rfc1952.txt for these values and
// detailed description of the logic
const int GZIP_HEADER_ID1 = 31;
const int GZIP_HEADER_ID2 = 139;
const int GZIP_HEADER_METHOD_DEFLATE = 8;
const unsigned int GZIP_HEADER_SIZE = 10;
const int GZIP_HEADER_FEXTRA = 1 << 2;
const int GZIP_HEADER_FNAME = 1 << 3;
const int GZIP_HEADER_COMMENT = 1 << 4;
const int GZIP_HEADER_CRC = 1 << 1;
typedef std::list<Request_ptr> RequestList; typedef std::list<Request_ptr> RequestList;
class Client::ClientPrivate
{
public:
std::string userAgent;
std::string proxy;
int proxyPort;
std::string proxyAuth;
NetChannelPoller poller;
unsigned int maxConnections;
RequestList pendingRequests;
// connections by host (potentially more than one)
ConnectionDict connections;
SGTimeStamp timeTransferSample;
unsigned int bytesTransferred;
unsigned int lastTransferRate;
uint64_t totalBytesDownloaded;
};
class Connection : public NetChat class Connection : public NetChat
{ {
@@ -61,26 +92,28 @@ public:
Connection(Client* pr) : Connection(Client* pr) :
client(pr), client(pr),
state(STATE_CLOSED), state(STATE_CLOSED),
port(DEFAULT_HTTP_PORT), port(DEFAULT_HTTP_PORT)
zlibInflateBuffer(NULL),
zlibInflateBufferSize(0),
zlibOutputBuffer(NULL)
{ {
} }
virtual ~Connection() virtual ~Connection()
{ {
if (zlibInflateBuffer) { }
free(zlibInflateBuffer);
} virtual void handleBufferRead (NetBuffer& buffer)
{
if (zlibOutputBuffer) { if( !activeRequest || !activeRequest->isComplete() )
free(zlibOutputBuffer); return NetChat::handleBufferRead(buffer);
}
// Request should be aborted (signaled by setting its state to complete).
// force the state to GETTING_BODY, to simplify logic in
// responseComplete and handleClose
state = STATE_GETTING_BODY;
responseComplete();
} }
void setServer(const string& h, short p) void setServer(const std::string& h, short p)
{ {
host = h; host = h;
port = p; port = p;
@@ -111,6 +144,7 @@ public:
SG_LOG(SG_IO, SG_INFO, "HTTP socket error"); SG_LOG(SG_IO, SG_INFO, "HTTP socket error");
activeRequest->setFailure(error, "socket error"); activeRequest->setFailure(error, "socket error");
activeRequest = NULL; activeRequest = NULL;
_contentDecoder.reset();
} }
state = STATE_SOCKET_ERROR; state = STATE_SOCKET_ERROR;
@@ -138,6 +172,7 @@ public:
sentRequests.erase(it); sentRequests.erase(it);
} }
activeRequest = NULL; activeRequest = NULL;
_contentDecoder.reset();
} }
state = STATE_CLOSED; state = STATE_CLOSED;
@@ -154,18 +189,37 @@ public:
sentRequests.clear(); sentRequests.clear();
} }
void handleTimeout()
{
NetChat::handleError(ETIMEDOUT);
if (activeRequest) {
SG_LOG(SG_IO, SG_DEBUG, "HTTP socket timeout");
activeRequest->setFailure(ETIMEDOUT, "socket timeout");
activeRequest = NULL;
_contentDecoder.reset();
}
state = STATE_SOCKET_ERROR;
}
void queueRequest(const Request_ptr& r) void queueRequest(const Request_ptr& r)
{ {
queuedRequests.push_back(r); queuedRequests.push_back(r);
tryStartNextRequest(); tryStartNextRequest();
} }
void beginResponse() void beginResponse()
{ {
assert(!sentRequests.empty()); assert(!sentRequests.empty());
assert(state == STATE_WAITING_FOR_RESPONSE);
activeRequest = sentRequests.front();
activeRequest->responseStart(buffer); activeRequest = sentRequests.front();
try {
activeRequest->responseStart(buffer);
} catch (sg_exception& e) {
handleError(EIO);
}
state = STATE_GETTING_HEADERS; state = STATE_GETTING_HEADERS;
buffer.clear(); buffer.clear();
if (activeRequest->responseCode() == 204) { if (activeRequest->responseCode() == 204) {
@@ -178,11 +232,15 @@ public:
bodyTransferSize = -1; bodyTransferSize = -1;
chunkedTransfer = false; chunkedTransfer = false;
contentGZip = contentDeflate = false; _contentDecoder.reset();
} }
void tryStartNextRequest() void tryStartNextRequest()
{ {
while( !queuedRequests.empty()
&& queuedRequests.front()->isComplete() )
queuedRequests.pop_front();
if (queuedRequests.empty()) { if (queuedRequests.empty()) {
idleTime.stamp(); idleTime.stamp();
return; return;
@@ -203,28 +261,28 @@ public:
Request_ptr r = queuedRequests.front(); Request_ptr r = queuedRequests.front();
r->requestStart(); r->requestStart();
requestBodyBytesToSend = r->requestBodyLength();
std::stringstream headerData;
stringstream headerData; std::string path = r->path();
string path = r->path();
assert(!path.empty()); assert(!path.empty());
string query = r->query(); std::string query = r->query();
string bodyData; std::string bodyData;
if (!client->proxyHost().empty()) { if (!client->proxyHost().empty()) {
path = r->scheme() + "://" + r->host() + r->path(); path = r->scheme() + "://" + r->host() + r->path();
} }
if (r->requestBodyType() == CONTENT_TYPE_URL_ENCODED) { if (r->bodyType() == CONTENT_TYPE_URL_ENCODED) {
headerData << r->method() << " " << path << " HTTP/1.1\r\n"; headerData << r->method() << " " << path << " HTTP/1.1\r\n";
bodyData = query.substr(1); // URL-encode, drop the leading '?' bodyData = query.substr(1); // URL-encode, drop the leading '?'
headerData << "Content-Type:" << CONTENT_TYPE_URL_ENCODED << "\r\n"; headerData << "Content-Type:" << CONTENT_TYPE_URL_ENCODED << "\r\n";
headerData << "Content-Length:" << bodyData.size() << "\r\n"; headerData << "Content-Length:" << bodyData.size() << "\r\n";
} else { } else {
headerData << r->method() << " " << path << query << " HTTP/1.1\r\n"; headerData << r->method() << " " << path << query << " HTTP/1.1\r\n";
if (requestBodyBytesToSend >= 0) { if( r->hasBodyData() )
headerData << "Content-Length:" << requestBodyBytesToSend << "\r\n"; {
headerData << "Content-Type:" << r->requestBodyType() << "\r\n"; headerData << "Content-Length:" << r->bodyLength() << "\r\n";
headerData << "Content-Type:" << r->bodyType() << "\r\n";
} }
} }
@@ -235,8 +293,8 @@ public:
headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n"; headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
} }
BOOST_FOREACH(string h, r->requestHeaders()) { BOOST_FOREACH(const StringMap::value_type& h, r->requestHeaders()) {
headerData << h << ": " << r->header(h) << "\r\n"; headerData << h.first << ": " << h.second << "\r\n";
} }
headerData << "\r\n"; // final CRLF to terminate the headers headerData << "\r\n"; // final CRLF to terminate the headers
@@ -251,170 +309,62 @@ public:
// drain down before trying to start any more requests. // drain down before trying to start any more requests.
return; return;
} }
while (requestBodyBytesToSend > 0) { if( r->hasBodyData() )
char buf[4096]; for(size_t body_bytes_sent = 0; body_bytes_sent < r->bodyLength();)
int len = r->getBodyData(buf, 4096); {
if (len > 0) { char buf[4096];
requestBodyBytesToSend -= len; size_t len = r->getBodyData(buf, body_bytes_sent, 4096);
if (!bufferSend(buf, len)) { if( len )
SG_LOG(SG_IO, SG_WARN, "overflow the HTTP::Connection output buffer"); {
state = STATE_SOCKET_ERROR; if( !bufferSend(buf, len) )
return; {
SG_LOG(SG_IO,
SG_WARN,
"overflow the HTTP::Connection output buffer");
state = STATE_SOCKET_ERROR;
return;
}
body_bytes_sent += len;
}
else
{
SG_LOG(SG_IO,
SG_WARN,
"HTTP asynchronous request body generation is unsupported");
break;
} }
// SG_LOG(SG_IO, SG_INFO, "sent body:\n" << string(buf, len) << "\n%%%%%%%%%");
} else {
SG_LOG(SG_IO, SG_WARN, "HTTP asynchronous request body generation is unsupported");
break;
} }
}
SG_LOG(SG_IO, SG_DEBUG, "did start request:" << r->url() << // SG_LOG(SG_IO, SG_INFO, "did start request:" << r->url() <<
"\n\t @ " << reinterpret_cast<void*>(r.ptr()) << // "\n\t @ " << reinterpret_cast<void*>(r.ptr()) <<
"\n\t on connection " << this); // "\n\t on connection " << this);
// successfully sent, remove from queue, and maybe send the next // successfully sent, remove from queue, and maybe send the next
queuedRequests.pop_front(); queuedRequests.pop_front();
sentRequests.push_back(r); sentRequests.push_back(r);
state = STATE_WAITING_FOR_RESPONSE;
// pipelining, let's maybe send the next request right away
// pipelining, let's maybe send the next request right away
tryStartNextRequest(); tryStartNextRequest();
} }
virtual void collectIncomingData(const char* s, int n) virtual void collectIncomingData(const char* s, int n)
{ {
idleTime.stamp(); idleTime.stamp();
if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_CHUNKED_BYTES)) { client->receivedBytes(static_cast<unsigned int>(n));
if (contentGZip || contentDeflate) {
expandCompressedData(s, n); if( (state == STATE_GETTING_BODY)
} else { || (state == STATE_GETTING_CHUNKED_BYTES) )
activeRequest->processBodyBytes(s, n); _contentDecoder.receivedBytes(s, n);
} else
} else { buffer.append(s, n);
buffer += string(s, n);
}
} }
void expandCompressedData(const char* s, int n)
{
int reqSize = n + zlib.avail_in;
if (reqSize > zlibInflateBufferSize) {
// reallocate
unsigned char* newBuf = (unsigned char*) malloc(reqSize);
memcpy(newBuf, zlib.next_in, zlib.avail_in);
memcpy(newBuf + zlib.avail_in, s, n);
free(zlibInflateBuffer);
zlibInflateBuffer = newBuf;
zlibInflateBufferSize = reqSize;
} else {
// important to use memmove here, since it's very likely
// the source and destination ranges overlap
memmove(zlibInflateBuffer, zlib.next_in, zlib.avail_in);
memcpy(zlibInflateBuffer + zlib.avail_in, s, n);
}
zlib.next_in = (unsigned char*) zlibInflateBuffer;
zlib.avail_in = reqSize;
zlib.next_out = zlibOutputBuffer;
zlib.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
if (contentGZip && !handleGZipHeader()) {
return;
}
int writtenSize = 0;
do {
int result = inflate(&zlib, Z_NO_FLUSH);
if (result == Z_OK || result == Z_STREAM_END) {
// nothing to do
} else {
SG_LOG(SG_IO, SG_WARN, "HTTP: got Zlib error:" << result);
return;
}
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlib.avail_out;
if (result == Z_STREAM_END) {
break;
}
} while ((writtenSize == 0) && (zlib.avail_in > 0));
if (writtenSize > 0) {
activeRequest->processBodyBytes((const char*) zlibOutputBuffer, writtenSize);
}
}
bool handleGZipHeader()
{
// we clear this down to contentDeflate once the GZip header has been seen
if (zlib.avail_in < GZIP_HEADER_SIZE) {
return false; // need more header bytes
}
if ((zlibInflateBuffer[0] != GZIP_HEADER_ID1) ||
(zlibInflateBuffer[1] != GZIP_HEADER_ID2) ||
(zlibInflateBuffer[2] != GZIP_HEADER_METHOD_DEFLATE))
{
return false; // invalid GZip header
}
char flags = zlibInflateBuffer[3];
unsigned int gzipHeaderSize = GZIP_HEADER_SIZE;
if (flags & GZIP_HEADER_FEXTRA) {
gzipHeaderSize += 2;
if (zlib.avail_in < gzipHeaderSize) {
return false; // need more header bytes
}
unsigned short extraHeaderBytes = *(reinterpret_cast<unsigned short*>(zlibInflateBuffer + GZIP_HEADER_FEXTRA));
if ( sgIsBigEndian() ) {
sgEndianSwap( &extraHeaderBytes );
}
gzipHeaderSize += extraHeaderBytes;
if (zlib.avail_in < gzipHeaderSize) {
return false; // need more header bytes
}
}
if (flags & GZIP_HEADER_FNAME) {
gzipHeaderSize++;
while (gzipHeaderSize <= zlib.avail_in) {
if (zlibInflateBuffer[gzipHeaderSize-1] == 0) {
break; // found terminating NULL character
}
}
}
if (flags & GZIP_HEADER_COMMENT) {
gzipHeaderSize++;
while (gzipHeaderSize <= zlib.avail_in) {
if (zlibInflateBuffer[gzipHeaderSize-1] == 0) {
break; // found terminating NULL character
}
}
}
if (flags & GZIP_HEADER_CRC) {
gzipHeaderSize += 2;
}
if (zlib.avail_in < gzipHeaderSize) {
return false; // need more header bytes
}
zlib.next_in += gzipHeaderSize;
zlib.avail_in -= gzipHeaderSize;
// now we've processed the GZip header, can decode as deflate
contentGZip = false;
contentDeflate = true;
return true;
}
virtual void foundTerminator(void) virtual void foundTerminator(void)
{ {
idleTime.stamp(); idleTime.stamp();
switch (state) { switch (state) {
case STATE_IDLE: case STATE_WAITING_FOR_RESPONSE:
beginResponse(); beginResponse();
break; break;
@@ -443,6 +393,9 @@ public:
buffer.clear(); buffer.clear();
break; break;
case STATE_IDLE:
SG_LOG(SG_IO, SG_WARN, "HTTP got data in IDLE state, bad server?");
default: default:
break; break;
} }
@@ -454,6 +407,7 @@ public:
return false; return false;
} }
assert(sentRequests.empty());
return idleTime.elapsedMSec() > 1000 * 10; // ten seconds return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
} }
@@ -500,37 +454,9 @@ private:
void processHeader() void processHeader()
{ {
string h = strutils::simplify(buffer); std::string h = strutils::simplify(buffer);
if (h.empty()) { // blank line terminates headers if (h.empty()) { // blank line terminates headers
headersComplete(); headersComplete();
if (contentGZip || contentDeflate) {
memset(&zlib, 0, sizeof(z_stream));
if (!zlibOutputBuffer) {
zlibOutputBuffer = (unsigned char*) malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
}
// NULLs means we'll get default alloc+free methods
// which is absolutely fine
zlib.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
zlib.next_out = zlibOutputBuffer;
if (inflateInit2(&zlib, ZLIB_INFLATE_WINDOW_BITS) != Z_OK) {
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
}
}
if (chunkedTransfer) {
state = STATE_GETTING_CHUNKED;
} else if (noMessageBody || (bodyTransferSize == 0)) {
// force the state to GETTING_BODY, to simplify logic in
// responseComplete and handleClose
state = STATE_GETTING_BODY;
responseComplete();
} else {
setByteCount(bodyTransferSize); // may be -1, that's fine
state = STATE_GETTING_BODY;
}
return; return;
} }
@@ -540,9 +466,9 @@ private:
return; return;
} }
string key = strutils::simplify(buffer.substr(0, colonPos)); std::string key = strutils::simplify(buffer.substr(0, colonPos));
string lkey = boost::to_lower_copy(key); std::string lkey = boost::to_lower_copy(key);
string value = strutils::strip(buffer.substr(colonPos + 1)); std::string value = strutils::strip(buffer.substr(colonPos + 1));
// only consider these if getting headers (as opposed to trailers // only consider these if getting headers (as opposed to trailers
// of a chunked transfer) // of a chunked transfer)
@@ -559,20 +485,14 @@ private:
} else if (lkey == "transfer-encoding") { } else if (lkey == "transfer-encoding") {
processTransferEncoding(value); processTransferEncoding(value);
} else if (lkey == "content-encoding") { } else if (lkey == "content-encoding") {
if (value == "gzip") { _contentDecoder.setEncoding(value);
contentGZip = true;
} else if (value == "deflate") {
contentDeflate = true;
} else if (value != "identity") {
SG_LOG(SG_IO, SG_WARN, "unsupported content encoding:" << value);
}
} }
} }
activeRequest->responseHeader(lkey, value); activeRequest->responseHeader(lkey, value);
} }
void processTransferEncoding(const string& te) void processTransferEncoding(const std::string& te)
{ {
if (te == "chunked") { if (te == "chunked") {
chunkedTransfer = true; chunkedTransfer = true;
@@ -623,17 +543,25 @@ private:
void headersComplete() void headersComplete()
{ {
activeRequest->responseHeadersComplete(); activeRequest->responseHeadersComplete();
_contentDecoder.initWithRequest(activeRequest);
if (chunkedTransfer) {
state = STATE_GETTING_CHUNKED;
} else if (noMessageBody || (bodyTransferSize == 0)) {
// force the state to GETTING_BODY, to simplify logic in
// responseComplete and handleClose
state = STATE_GETTING_BODY;
responseComplete();
} else {
setByteCount(bodyTransferSize); // may be -1, that's fine
state = STATE_GETTING_BODY;
}
} }
void responseComplete() void responseComplete()
{ {
// SG_LOG(SG_IO, SG_INFO, "*** responseComplete:" << activeRequest->url()); Request_ptr completedRequest = activeRequest;
activeRequest->responseComplete(); _contentDecoder.finish();
client->requestFinished(this);
if (contentDeflate) {
inflateEnd(&zlib);
}
assert(sentRequests.front() == activeRequest); assert(sentRequests.front() == activeRequest);
sentRequests.pop_front(); sentRequests.pop_front();
@@ -644,21 +572,29 @@ private:
if (doClose) { if (doClose) {
// this will bring us into handleClose() above, which updates // this will bring us into handleClose() above, which updates
// state to STATE_CLOSED // state to STATE_CLOSED
close(); close();
// if we have additional requests waiting, try to start them now // if we have additional requests waiting, try to start them now
tryStartNextRequest(); tryStartNextRequest();
} }
} }
if (state != STATE_CLOSED) { if (state != STATE_CLOSED) {
state = STATE_IDLE; state = sentRequests.empty() ? STATE_IDLE : STATE_WAITING_FOR_RESPONSE;
} }
setTerminator("\r\n");
// notify request after we change state, so this connection is idle
// if completion triggers other requests (which is likely)
// SG_LOG(SG_IO, SG_INFO, "*** responseComplete:" << activeRequest->url());
completedRequest->responseComplete();
client->requestFinished(this);
setTerminator("\r\n");
} }
enum ConnectionState { enum ConnectionState {
STATE_IDLE = 0, STATE_IDLE = 0,
STATE_WAITING_FOR_RESPONSE,
STATE_GETTING_HEADERS, STATE_GETTING_HEADERS,
STATE_GETTING_BODY, STATE_GETTING_BODY,
STATE_GETTING_CHUNKED, STATE_GETTING_CHUNKED,
@@ -671,75 +607,187 @@ private:
Client* client; Client* client;
Request_ptr activeRequest; Request_ptr activeRequest;
ConnectionState state; ConnectionState state;
string host; std::string host;
short port; short port;
std::string buffer; std::string buffer;
int bodyTransferSize; int bodyTransferSize;
SGTimeStamp idleTime; SGTimeStamp idleTime;
bool chunkedTransfer; bool chunkedTransfer;
bool noMessageBody; bool noMessageBody;
int requestBodyBytesToSend;
z_stream zlib;
unsigned char* zlibInflateBuffer;
int zlibInflateBufferSize;
unsigned char* zlibOutputBuffer;
bool contentGZip, contentDeflate;
RequestList queuedRequests; RequestList queuedRequests;
RequestList sentRequests; RequestList sentRequests;
ContentDecoder _contentDecoder;
}; };
Client::Client() Client::Client() :
d(new ClientPrivate)
{ {
d->proxyPort = 0;
d->maxConnections = 4;
d->bytesTransferred = 0;
d->lastTransferRate = 0;
d->timeTransferSample.stamp();
d->totalBytesDownloaded = 0;
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION)); setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
} }
Client::~Client()
{
}
void Client::setMaxConnections(unsigned int maxCon)
{
if (maxCon < 1) {
throw sg_range_exception("illegal HTTP::Client::setMaxConnections value");
}
d->maxConnections = maxCon;
}
void Client::update(int waitTimeout) void Client::update(int waitTimeout)
{ {
_poller.poll(waitTimeout); if (!d->poller.hasChannels() && (waitTimeout > 0)) {
SGTimeStamp::sleepForMSec(waitTimeout);
ConnectionDict::iterator it = _connections.begin(); } else {
for (; it != _connections.end(); ) { d->poller.poll(waitTimeout);
if (it->second->hasIdleTimeout() || it->second->hasError() || }
it->second->hasErrorTimeout())
bool waitingRequests = !d->pendingRequests.empty();
ConnectionDict::iterator it = d->connections.begin();
for (; it != d->connections.end(); ) {
Connection* con = it->second;
if (con->hasIdleTimeout() ||
con->hasError() ||
con->hasErrorTimeout() ||
(!con->isActive() && waitingRequests))
{ {
if (con->hasErrorTimeout()) {
// tell the connection we're timing it out
con->handleTimeout();
}
// connection has been idle for a while, clean it up // connection has been idle for a while, clean it up
// (or has an error condition, again clean it up) // (or if we have requests waiting for a different host,
// or an error condition
ConnectionDict::iterator del = it++; ConnectionDict::iterator del = it++;
delete del->second; delete del->second;
_connections.erase(del); d->connections.erase(del);
} else { } else {
if (it->second->shouldStartNext()) { if (it->second->shouldStartNext()) {
it->second->tryStartNextRequest(); it->second->tryStartNextRequest();
} }
++it; ++it;
} }
} // of connecion iteration } // of connection iteration
if (waitingRequests && (d->connections.size() < d->maxConnections)) {
RequestList waiting(d->pendingRequests);
d->pendingRequests.clear();
// re-submit all waiting requests in order; this takes care of
// finding multiple pending items targetted to the same (new)
// connection
BOOST_FOREACH(Request_ptr req, waiting) {
makeRequest(req);
}
}
} }
void Client::makeRequest(const Request_ptr& r) void Client::makeRequest(const Request_ptr& r)
{ {
string host = r->host(); if( r->isComplete() )
return;
if( r->url().find("://") == std::string::npos ) {
r->setFailure(EINVAL, "malformed URL");
return;
}
if( r->url().find("http://") != 0 ) {
r->setFailure(EINVAL, "only HTTP protocol is supported");
return;
}
std::string host = r->host();
int port = r->port(); int port = r->port();
if (!_proxy.empty()) { if (!d->proxy.empty()) {
host = _proxy; host = d->proxy;
port = _proxyPort; port = d->proxyPort;
} }
stringstream ss; Connection* con = NULL;
std::stringstream ss;
ss << host << "-" << port; ss << host << "-" << port;
string connectionId = ss.str(); std::string connectionId = ss.str();
bool havePending = !d->pendingRequests.empty();
if (_connections.find(connectionId) == _connections.end()) { bool atConnectionsLimit = d->connections.size() >= d->maxConnections;
Connection* con = new Connection(this); ConnectionDict::iterator consEnd = d->connections.end();
con->setServer(host, port);
_poller.addChannel(con); // assign request to an existing Connection.
_connections[connectionId] = con; // various options exist here, examined in order
ConnectionDict::iterator it = d->connections.find(connectionId);
if (atConnectionsLimit && (it == consEnd)) {
// maximum number of connections active, queue this request
// when a connection goes inactive, we'll start this one
d->pendingRequests.push_back(r);
return;
} }
_connections[connectionId]->queueRequest(r); // scan for an idle Connection to the same host (likely if we're
// retrieving multiple resources from the same host in quick succession)
// if we have pending requests (waiting for a free Connection), then
// force new requests on this id to always use the first Connection
// (instead of the random selection below). This ensures that when
// there's pressure on the number of connections to keep alive, one
// host can't DoS every other.
int count = 0;
for (; (it != consEnd) && (it->first == connectionId); ++it, ++count) {
if (havePending || !it->second->isActive()) {
con = it->second;
break;
}
}
if (!con && atConnectionsLimit) {
// all current connections are busy (active), and we don't
// have free connections to allocate, so let's assign to
// an existing one randomly. Ideally we'd used whichever one will
// complete first but we don't have that info.
int index = rand() % count;
for (it = d->connections.find(connectionId); index > 0; --index) { ; }
con = it->second;
}
// allocate a new connection object
if (!con) {
con = new Connection(this);
con->setServer(host, port);
d->poller.addChannel(con);
d->connections.insert(d->connections.end(),
ConnectionDict::value_type(connectionId, con));
}
con->queueRequest(r);
}
//------------------------------------------------------------------------------
FileRequestRef Client::save( const std::string& url,
const std::string& filename )
{
FileRequestRef req = new FileRequest(url, filename);
makeRequest(req);
return req;
}
//------------------------------------------------------------------------------
MemoryRequestRef Client::load(const std::string& url)
{
MemoryRequestRef req = new MemoryRequest(url);
makeRequest(req);
return req;
} }
void Client::requestFinished(Connection* con) void Client::requestFinished(Connection* con)
@@ -747,28 +795,82 @@ void Client::requestFinished(Connection* con)
} }
void Client::setUserAgent(const string& ua) void Client::setUserAgent(const std::string& ua)
{ {
_userAgent = ua; d->userAgent = ua;
} }
void Client::setProxy(const string& proxy, int port, const string& auth) const std::string& Client::userAgent() const
{ {
_proxy = proxy; return d->userAgent;
_proxyPort = port; }
_proxyAuth = auth;
const std::string& Client::proxyHost() const
{
return d->proxy;
}
const std::string& Client::proxyAuth() const
{
return d->proxyAuth;
}
void Client::setProxy( const std::string& proxy,
int port,
const std::string& auth )
{
d->proxy = proxy;
d->proxyPort = port;
d->proxyAuth = auth;
} }
bool Client::hasActiveRequests() const bool Client::hasActiveRequests() const
{ {
ConnectionDict::const_iterator it = _connections.begin(); ConnectionDict::const_iterator it = d->connections.begin();
for (; it != _connections.end(); ++it) { for (; it != d->connections.end(); ++it) {
if (it->second->isActive()) return true; if (it->second->isActive()) return true;
} }
return false; return false;
} }
void Client::receivedBytes(unsigned int count)
{
d->bytesTransferred += count;
d->totalBytesDownloaded += count;
}
unsigned int Client::transferRateBytesPerSec() const
{
unsigned int e = d->timeTransferSample.elapsedMSec();
if (e > 400) {
// too long a window, ignore
d->timeTransferSample.stamp();
d->bytesTransferred = 0;
d->lastTransferRate = 0;
return 0;
}
if (e < 100) { // avoid really narrow windows
return d->lastTransferRate;
}
unsigned int ratio = (d->bytesTransferred * 1000) / e;
// run a low-pass filter
unsigned int smoothed = ((400 - e) * d->lastTransferRate) + (e * ratio);
smoothed /= 400;
d->timeTransferSample.stamp();
d->bytesTransferred = 0;
d->lastTransferRate = smoothed;
return smoothed;
}
uint64_t Client::totalBytesDownloaded() const
{
return d->totalBytesDownloaded;
}
} // of namespace HTTP } // of namespace HTTP
} // of namespace simgear } // of namespace simgear
+74 -21
View File
@@ -1,10 +1,34 @@
/**
* \file HTTPClient.hxx - simple HTTP client engine for SimHear
*/
// Written by James Turner
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef SG_HTTP_CLIENT_HXX #ifndef SG_HTTP_CLIENT_HXX
#define SG_HTTP_CLIENT_HXX #define SG_HTTP_CLIENT_HXX
#include <map> #include <memory> // for std::auto_ptr
#include <stdint.h> // for uint_64t
#include <simgear/io/HTTPRequest.hxx> #include <simgear/io/HTTPFileRequest.hxx>
#include <simgear/io/sg_netChannel.hxx> #include <simgear/io/HTTPMemoryRequest.hxx>
namespace simgear namespace simgear
{ {
@@ -12,49 +36,78 @@ namespace simgear
namespace HTTP namespace HTTP
{ {
// forward decls
class Connection; class Connection;
class Client class Client
{ {
public: public:
Client(); Client();
~Client();
void update(int waitTimeout = 0); void update(int waitTimeout = 0);
void makeRequest(const Request_ptr& r); void makeRequest(const Request_ptr& r);
/**
* Download a resource and save it to a file.
*
* @param url The resource to download
* @param filename Path to the target file
* @param data Data for POST request
*/
FileRequestRef save( const std::string& url,
const std::string& filename );
/**
* Request a resource and keep it in memory.
*
* @param url The resource to download
*/
MemoryRequestRef load(const std::string& url);
void setUserAgent(const std::string& ua); void setUserAgent(const std::string& ua);
void setProxy(const std::string& proxy, int port, const std::string& auth = ""); void setProxy(const std::string& proxy, int port, const std::string& auth = "");
const std::string& userAgent() const /**
{ return _userAgent; } * Specify the maximum permitted simultaneous connections
* (default value is 1)
*/
void setMaxConnections(unsigned int maxCons);
const std::string& userAgent() const;
const std::string& proxyHost() const const std::string& proxyHost() const;
{ return _proxy; }
const std::string& proxyAuth() const const std::string& proxyAuth() const;
{ return _proxyAuth; }
/** /**
* predicate, check if at least one connection is active, with at * predicate, check if at least one connection is active, with at
* least one request active or queued. * least one request active or queued.
*/ */
bool hasActiveRequests() const; bool hasActiveRequests() const;
/**
* crude tracking of bytes-per-second transferred over the socket.
* suitable for user feedback and rough profiling, nothing more.
*/
unsigned int transferRateBytesPerSec() const;
/**
* total bytes downloaded by this HTTP client, for bandwidth usage
* monitoring
*/
uint64_t totalBytesDownloaded() const;
private: private:
void requestFinished(Connection* con); void requestFinished(Connection* con);
void receivedBytes(unsigned int count);
friend class Connection; friend class Connection;
friend class Request; friend class Request;
std::string _userAgent; class ClientPrivate;
std::string _proxy; std::auto_ptr<ClientPrivate> d;
int _proxyPort;
std::string _proxyAuth;
NetChannelPoller _poller;
// connections by host
typedef std::map<std::string, Connection*> ConnectionDict;
ConnectionDict _connections;
}; };
} // of namespace HTTP } // of namespace HTTP
+269
View File
@@ -0,0 +1,269 @@
// Written by James Turner
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include "HTTPContentDecode.hxx"
#include <cassert>
#include <cstdlib> // rand()
#include <cstring> // for memset, memcpy
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/io/lowlevel.hxx> // for sgEndian stuff
namespace simgear
{
namespace HTTP
{
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
const int ZLIB_INFLATE_WINDOW_BITS = -MAX_WBITS;
// see http://www.ietf.org/rfc/rfc1952.txt for these values and
// detailed description of the logic
const int GZIP_HEADER_ID1 = 31;
const int GZIP_HEADER_ID2 = 139;
const int GZIP_HEADER_METHOD_DEFLATE = 8;
const unsigned int GZIP_HEADER_SIZE = 10;
const int GZIP_HEADER_FEXTRA = 1 << 2;
const int GZIP_HEADER_FNAME = 1 << 3;
const int GZIP_HEADER_COMMENT = 1 << 4;
const int GZIP_HEADER_CRC = 1 << 1;
ContentDecoder::ContentDecoder() :
_output(NULL),
_zlib(NULL),
_input(NULL),
_inputAllocated(0),
_inputSize(0)
{
}
ContentDecoder::~ContentDecoder()
{
free(_output);
free(_input);
free(_zlib);
}
void ContentDecoder::setEncoding(const std::string& encoding)
{
if (encoding == "gzip") {
_contentDeflate = true;
_needGZipHeader = true;
} else if (encoding == "deflate") {
_contentDeflate = true;
_needGZipHeader = false;
} else if (encoding != "identity") {
SG_LOG(SG_IO, SG_WARN, "unsupported content encoding:" << encoding);
}
}
void ContentDecoder::reset()
{
_request = NULL;
_contentDeflate = false;
_needGZipHeader = false;
_inputSize = 0;
}
void ContentDecoder::initWithRequest(Request_ptr req)
{
_request = req;
if (!_contentDeflate) {
return;
}
if (!_zlib) {
_zlib = (z_stream*) malloc(sizeof(z_stream));
}
memset(_zlib, 0, sizeof(z_stream));
if (!_output) {
_output = (unsigned char*) malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
}
_inputSize = 0;
// NULLs means we'll get default alloc+free methods
// which is absolutely fine
_zlib->avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
_zlib->next_out = _output;
if (inflateInit2(_zlib, ZLIB_INFLATE_WINDOW_BITS) != Z_OK) {
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
}
}
void ContentDecoder::finish()
{
if (_contentDeflate) {
runDecoder();
inflateEnd(_zlib);
}
}
void ContentDecoder::receivedBytes(const char* n, size_t s)
{
if (!_contentDeflate) {
_request->processBodyBytes(n, s);
return;
}
// allocate more space if needed (this will only happen rarely once the
// buffer has hit something proportionate to the server's compression
// window size)
size_t requiredSize = _inputSize + s;
if (requiredSize > _inputAllocated) {
reallocateInputBuffer(requiredSize);
}
// copy newly recieved bytes into the buffer
memcpy(_input + _inputSize, n, s);
_inputSize += s;
if (_needGZipHeader && !consumeGZipHeader()) {
// still waiting on the full GZIP header, so done
return;
}
runDecoder();
}
void ContentDecoder::consumeBytes(size_t consumed)
{
assert(_inputSize >= consumed);
// move existing (consumed) bytes down
if (consumed > 0) {
size_t newSize = _inputSize - consumed;
memmove(_input, _input + consumed, newSize);
_inputSize = newSize;
}
}
void ContentDecoder::reallocateInputBuffer(size_t newSize)
{
_input = (unsigned char*) realloc(_input, newSize);
_inputAllocated = newSize;
}
void ContentDecoder::runDecoder()
{
_zlib->next_in = (unsigned char*) _input;
_zlib->avail_in = _inputSize;
int writtenSize;
// loop, running zlib() inflate and sending output bytes to
// our request body handler. Keep calling inflate until no bytes are
// written, and ZLIB has consumed all available input
do {
_zlib->next_out = _output;
_zlib->avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
int result = inflate(_zlib, Z_NO_FLUSH);
if (result == Z_OK || result == Z_STREAM_END) {
// nothing to do
} else if (result == Z_BUF_ERROR) {
// transient error, fall through
} else {
// _error = result;
return;
}
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - _zlib->avail_out;
if (writtenSize > 0) {
_request->processBodyBytes((char*) _output, writtenSize);
}
if (result == Z_STREAM_END) {
break;
}
} while ((_zlib->avail_in > 0) || (writtenSize > 0));
// update input buffers based on what we consumed
consumeBytes(_inputSize - _zlib->avail_in);
}
bool ContentDecoder::consumeGZipHeader()
{
size_t avail = _inputSize;
if (avail < GZIP_HEADER_SIZE) {
return false; // need more header bytes
}
if ((_input[0] != GZIP_HEADER_ID1) ||
(_input[1] != GZIP_HEADER_ID2) ||
(_input[2] != GZIP_HEADER_METHOD_DEFLATE))
{
return false; // invalid GZip header
}
char flags = _input[3];
unsigned int gzipHeaderSize = GZIP_HEADER_SIZE;
if (flags & GZIP_HEADER_FEXTRA) {
gzipHeaderSize += 2;
if (avail < gzipHeaderSize) {
return false; // need more header bytes
}
unsigned short extraHeaderBytes = *(reinterpret_cast<unsigned short*>(_input + GZIP_HEADER_FEXTRA));
if ( sgIsBigEndian() ) {
sgEndianSwap( &extraHeaderBytes );
}
gzipHeaderSize += extraHeaderBytes;
if (avail < gzipHeaderSize) {
return false; // need more header bytes
}
}
#if 0
if (flags & GZIP_HEADER_FNAME) {
gzipHeaderSize++;
while (gzipHeaderSize <= avail) {
if (_input[gzipHeaderSize-1] == 0) {
break; // found terminating NULL character
}
}
}
if (flags & GZIP_HEADER_COMMENT) {
gzipHeaderSize++;
while (gzipHeaderSize <= avail) {
if (_input[gzipHeaderSize-1] == 0) {
break; // found terminating NULL character
}
}
}
#endif
if (flags & GZIP_HEADER_CRC) {
gzipHeaderSize += 2;
}
if (avail < gzipHeaderSize) {
return false; // need more header bytes
}
consumeBytes(gzipHeaderSize);
_needGZipHeader = false;
return true;
}
} // of namespace HTTP
} // of namespace simgear
+72
View File
@@ -0,0 +1,72 @@
// Written by James Turner
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef SG_HTTP_CONTENT_DECODER_HXX
#define SG_HTTP_CONTENT_DECODER_HXX
#include <string>
#include <zlib.h>
#include <simgear/io/HTTPRequest.hxx>
namespace simgear
{
namespace HTTP
{
class ContentDecoder
{
public:
ContentDecoder();
~ContentDecoder();
void reset();
void initWithRequest(Request_ptr req);
void finish();
void setEncoding(const std::string& encoding);
void receivedBytes(const char* n, size_t s);
private:
bool consumeGZipHeader();
void runDecoder();
void consumeBytes(size_t consumed);
void reallocateInputBuffer(size_t newSize);
Request_ptr _request;
unsigned char* _output;
z_stream* _zlib;
unsigned char* _input;
size_t _inputAllocated, _inputSize;
bool _contentDeflate, _needGZipHeader;
};
} // of namespace HTTP
} // of namespace simgear
#endif // of SG_HTTP_CONTENT_DECODER_HXX
+91
View File
@@ -0,0 +1,91 @@
// HTTP request writing response to a file.
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "HTTPFileRequest.hxx"
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
namespace simgear
{
namespace HTTP
{
//----------------------------------------------------------------------------
FileRequest::FileRequest(const std::string& url, const std::string& path):
Request(url, "GET"),
_filename(path)
{
}
//----------------------------------------------------------------------------
void FileRequest::responseHeadersComplete()
{
Request::responseHeadersComplete();
if( responseCode() != 200 )
return setFailure(responseCode(), responseReason());
if( !_filename.empty() )
{
// TODO validate path? (would require to expose fgValidatePath somehow to
// simgear)
SGPath path(_filename);
path.create_dir(0755);
_file.open(_filename.c_str(), std::ios::binary | std::ios::trunc);
}
if( !_file )
{
SG_LOG
(
SG_IO,
SG_WARN,
"HTTP::FileRequest: failed to open file '" << _filename << "'"
);
abort("Failed to open file.");
}
}
//----------------------------------------------------------------------------
void FileRequest::gotBodyData(const char* s, int n)
{
if( !_file )
{
SG_LOG
(
SG_IO,
SG_DEBUG,
"HTTP::FileRequest: received data for closed file '" << _filename << "'"
);
return;
}
_file.write(s, n);
}
//----------------------------------------------------------------------------
void FileRequest::onAlways()
{
_file.close();
}
} // namespace HTTP
} // namespace simgear
+56
View File
@@ -0,0 +1,56 @@
///@file HTTP request writing response to a file.
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_HTTP_FILEREQUEST_HXX_
#define SG_HTTP_FILEREQUEST_HXX_
#include "HTTPRequest.hxx"
#include <fstream>
namespace simgear
{
namespace HTTP
{
class FileRequest:
public Request
{
public:
/**
*
* @param url Adress to download from
* @param path Path to file for saving response
*/
FileRequest(const std::string& url, const std::string& path);
protected:
std::string _filename;
std::ofstream _file;
virtual void responseHeadersComplete();
virtual void gotBodyData(const char* s, int n);
virtual void onAlways();
};
typedef SGSharedPtr<FileRequest> FileRequestRef;
} // namespace HTTP
} // namespace simgear
#endif /* SG_HTTP_FILEREQUEST_HXX_ */
+55
View File
@@ -0,0 +1,55 @@
// HTTP request keeping response in memory.
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "HTTPMemoryRequest.hxx"
namespace simgear
{
namespace HTTP
{
//----------------------------------------------------------------------------
MemoryRequest::MemoryRequest(const std::string& url):
Request(url, "GET")
{
}
//----------------------------------------------------------------------------
const std::string& MemoryRequest::responseBody() const
{
return _response;
}
//----------------------------------------------------------------------------
void MemoryRequest::responseHeadersComplete()
{
Request::responseHeadersComplete();
if( responseLength() )
_response.reserve( responseLength() );
}
//----------------------------------------------------------------------------
void MemoryRequest::gotBodyData(const char* s, int n)
{
_response.append(s, n);
}
} // namespace HTTP
} // namespace simgear
+58
View File
@@ -0,0 +1,58 @@
///@file HTTP request keeping response in memory.
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_HTTP_MEMORYREQUEST_HXX_
#define SG_HTTP_MEMORYREQUEST_HXX_
#include "HTTPRequest.hxx"
#include <fstream>
namespace simgear
{
namespace HTTP
{
class MemoryRequest:
public Request
{
public:
/**
*
* @param url Adress to download from
*/
MemoryRequest(const std::string& url);
/**
* Body contents of server response.
*/
const std::string& responseBody() const;
protected:
std::string _response;
virtual void responseHeadersComplete();
virtual void gotBodyData(const char* s, int n);
};
typedef SGSharedPtr<MemoryRequest> MemoryRequestRef;
} // namespace HTTP
} // namespace simgear
#endif /* SG_HTTP_MEMORYREQUEST_HXX_ */
+259 -117
View File
@@ -1,104 +1,190 @@
#include "HTTPRequest.hxx" #include "HTTPRequest.hxx"
#include <simgear/misc/strutils.hxx>
#include <simgear/compiler.h> #include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
using std::string; #include <simgear/props/props_io.hxx>
using std::map; #include <simgear/structure/exception.hxx>
namespace simgear namespace simgear
{ {
namespace HTTP namespace HTTP
{ {
extern const int DEFAULT_HTTP_PORT; extern const int DEFAULT_HTTP_PORT;
Request::Request(const string& url, const string method) : //------------------------------------------------------------------------------
_method(method), Request::Request(const std::string& url, const std::string method):
_url(url), _method(method),
_responseVersion(HTTP_VERSION_UNKNOWN), _url(url),
_responseStatus(0), _responseVersion(HTTP_VERSION_UNKNOWN),
_responseLength(0), _responseStatus(0),
_receivedBodyBytes(0), _responseLength(0),
_willClose(false) _receivedBodyBytes(0),
_ready_state(UNSENT),
_willClose(false)
{ {
} }
//------------------------------------------------------------------------------
Request::~Request() Request::~Request()
{ {
} }
void Request::setUrl(const string& url) //------------------------------------------------------------------------------
Request* Request::done(const Callback& cb)
{ {
_url = url; if( _ready_state == DONE )
cb(this);
else
_cb_done = cb;
return this;
} }
string_list Request::requestHeaders() const //------------------------------------------------------------------------------
Request* Request::fail(const Callback& cb)
{ {
string_list r; if( _ready_state == FAILED )
return r; cb(this);
else
_cb_fail = cb;
return this;
} }
string Request::header(const std::string& name) const //------------------------------------------------------------------------------
Request* Request::always(const Callback& cb)
{ {
return string(); if( isComplete() )
cb(this);
else
_cb_always = cb;
return this;
} }
//------------------------------------------------------------------------------
void Request::setBodyData( const std::string& data,
const std::string& type )
{
_request_data = data;
_request_media_type = type;
if( !data.empty() && _method == "GET" )
_method = "POST";
}
//----------------------------------------------------------------------------
void Request::setBodyData(const SGPropertyNode* data)
{
if( !data )
setBodyData("");
std::stringstream buf;
writeProperties(buf, data, true);
setBodyData(buf.str(), "application/xml");
}
//------------------------------------------------------------------------------
void Request::setUrl(const std::string& url)
{
_url = url;
}
//------------------------------------------------------------------------------
void Request::requestStart() void Request::requestStart()
{ {
setReadyState(OPENED);
} }
void Request::responseStart(const string& r) //------------------------------------------------------------------------------
Request::HTTPVersion decodeHTTPVersion(const std::string& v)
{
if( v == "HTTP/1.1" ) return Request::HTTP_1_1;
if( v == "HTTP/1.0" ) return Request::HTTP_1_0;
if( strutils::starts_with(v, "HTTP/0.") ) return Request::HTTP_0_x;
return Request::HTTP_VERSION_UNKNOWN;
}
//------------------------------------------------------------------------------
void Request::responseStart(const std::string& r)
{ {
const int maxSplit = 2; // HTTP/1.1 nnn reason-string const int maxSplit = 2; // HTTP/1.1 nnn reason-string
string_list parts = strutils::split(r, NULL, maxSplit); string_list parts = strutils::split(r, NULL, maxSplit);
if (parts.size() != 3) { if (parts.size() != 3) {
SG_LOG(SG_IO, SG_WARN, "HTTP::Request: malformed response start:" << r); throw sg_io_exception("bad HTTP response");
setFailure(400, "malformed HTTP response header");
return;
} }
_responseVersion = decodeVersion(parts[0]); _responseVersion = decodeHTTPVersion(parts[0]);
_responseStatus = strutils::to_int(parts[1]); _responseStatus = strutils::to_int(parts[1]);
_responseReason = parts[2]; _responseReason = parts[2];
} }
void Request::responseHeader(const string& key, const string& value) //------------------------------------------------------------------------------
void Request::responseHeader(const std::string& key, const std::string& value)
{ {
if (key == "connection") { if( key == "connection" )
_willClose = (value.find("close") != string::npos); _willClose = (value.find("close") != std::string::npos);
}
_responseHeaders[key] = value;
_responseHeaders[key] = value;
} }
//------------------------------------------------------------------------------
void Request::responseHeadersComplete() void Request::responseHeadersComplete()
{ {
// no op setReadyState(HEADERS_RECEIVED);
}
void Request::processBodyBytes(const char* s, int n)
{
_receivedBodyBytes += n;
gotBodyData(s, n);
}
void Request::gotBodyData(const char* s, int n)
{
} }
//------------------------------------------------------------------------------
void Request::responseComplete() void Request::responseComplete()
{ {
if( !isComplete() )
setReadyState(DONE);
} }
string Request::scheme() const //------------------------------------------------------------------------------
void Request::gotBodyData(const char* s, int n)
{
setReadyState(LOADING);
}
//------------------------------------------------------------------------------
void Request::onDone()
{
}
//------------------------------------------------------------------------------
void Request::onFail()
{
SG_LOG
(
SG_IO,
SG_INFO,
"request failed:" << url() << " : "
<< responseCode() << "/" << responseReason()
);
}
//------------------------------------------------------------------------------
void Request::onAlways()
{
}
//------------------------------------------------------------------------------
void Request::processBodyBytes(const char* s, int n)
{
_receivedBodyBytes += n;
gotBodyData(s, n);
}
//------------------------------------------------------------------------------
std::string Request::scheme() const
{ {
int firstColon = url().find(":"); int firstColon = url().find(":");
if (firstColon > 0) { if (firstColon > 0) {
@@ -107,10 +193,11 @@ string Request::scheme() const
return ""; // couldn't parse scheme return ""; // couldn't parse scheme
} }
string Request::path() const //------------------------------------------------------------------------------
std::string Request::path() const
{ {
string u(url()); std::string u(url());
int schemeEnd = u.find("://"); int schemeEnd = u.find("://");
if (schemeEnd < 0) { if (schemeEnd < 0) {
return ""; // couldn't parse scheme return ""; // couldn't parse scheme
@@ -132,10 +219,10 @@ string Request::path() const
return u.substr(hostEnd, query - hostEnd); return u.substr(hostEnd, query - hostEnd);
} }
//------------------------------------------------------------------------------
string Request::query() const std::string Request::query() const
{ {
string u(url()); std::string u(url());
int query = u.find('?'); int query = u.find('?');
if (query < 0) { if (query < 0) {
return ""; //no query string found return ""; //no query string found
@@ -144,104 +231,159 @@ string Request::query() const
return u.substr(query); //includes question mark return u.substr(query); //includes question mark
} }
//------------------------------------------------------------------------------
std::string Request::host() const
string Request::host() const
{ {
string hp(hostAndPort()); std::string hp(hostAndPort());
int colonPos = hp.find(':'); int colonPos = hp.find(':');
if (colonPos >= 0) { if (colonPos >= 0) {
return hp.substr(0, colonPos); // trim off the colon and port return hp.substr(0, colonPos); // trim off the colon and port
} else { } else {
return hp; // no port specifier return hp; // no port specifier
} }
} }
//------------------------------------------------------------------------------
unsigned short Request::port() const unsigned short Request::port() const
{ {
string hp(hostAndPort()); std::string hp(hostAndPort());
int colonPos = hp.find(':'); int colonPos = hp.find(':');
if (colonPos >= 0) { if (colonPos >= 0) {
return (unsigned short) strutils::to_int(hp.substr(colonPos + 1)); return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
} else { } else {
return DEFAULT_HTTP_PORT; return DEFAULT_HTTP_PORT;
} }
} }
string Request::hostAndPort() const //------------------------------------------------------------------------------
std::string Request::hostAndPort() const
{ {
string u(url()); std::string u(url());
int schemeEnd = u.find("://"); int schemeEnd = u.find("://");
if (schemeEnd < 0) { if (schemeEnd < 0) {
return ""; // couldn't parse scheme return ""; // couldn't parse scheme
} }
int hostEnd = u.find('/', schemeEnd + 3); int hostEnd = u.find('/', schemeEnd + 3);
if (hostEnd < 0) { // all remainder of URL is host if (hostEnd < 0) { // all remainder of URL is host
return u.substr(schemeEnd + 3); return u.substr(schemeEnd + 3);
} }
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3)); return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
} }
//------------------------------------------------------------------------------
void Request::setResponseLength(unsigned int l) void Request::setResponseLength(unsigned int l)
{ {
_responseLength = l; _responseLength = l;
} }
//------------------------------------------------------------------------------
unsigned int Request::responseLength() const unsigned int Request::responseLength() const
{ {
// if the server didn't supply a content length, use the number // if the server didn't supply a content length, use the number
// of bytes we actually received (so far) // of bytes we actually received (so far)
if ((_responseLength == 0) && (_receivedBodyBytes > 0)) { if( (_responseLength == 0) && (_receivedBodyBytes > 0) )
return _receivedBodyBytes; return _receivedBodyBytes;
}
return _responseLength;
return _responseLength;
} }
//------------------------------------------------------------------------------
void Request::setFailure(int code, const std::string& reason) void Request::setFailure(int code, const std::string& reason)
{ {
_responseStatus = code; _responseStatus = code;
_responseReason = reason; _responseReason = reason;
failed(); setReadyState(FAILED);
} }
void Request::failed() //------------------------------------------------------------------------------
void Request::setReadyState(ReadyState state)
{ {
// no-op in base class _ready_state = state;
SG_LOG(SG_IO, SG_INFO, "request failed:" << url()); if( state == DONE )
{
// Finish C++ part of request to ensure everything is finished (for example
// files and streams are closed) before calling any callback (possibly using
// such files)
onDone();
onAlways();
if( _cb_done )
_cb_done(this);
}
else if( state == FAILED )
{
onFail();
onAlways();
if( _cb_fail )
_cb_fail(this);
}
else
return;
if( _cb_always )
_cb_always(this);
} }
Request::HTTPVersion Request::decodeVersion(const string& v) //------------------------------------------------------------------------------
void Request::abort()
{ {
if (v == "HTTP/1.1") return HTTP_1_1; abort("Request aborted.");
if (v == "HTTP/1.0") return HTTP_1_0;
if (strutils::starts_with(v, "HTTP/0.")) return HTTP_0_x;
return HTTP_VERSION_UNKNOWN;
} }
//----------------------------------------------------------------------------
void Request::abort(const std::string& reason)
{
if( isComplete() )
return;
setFailure(-1, reason);
_willClose = true;
}
//------------------------------------------------------------------------------
bool Request::closeAfterComplete() const bool Request::closeAfterComplete() const
{ {
// for non HTTP/1.1 connections, assume server closes // for non HTTP/1.1 connections, assume server closes
return _willClose || (_responseVersion != HTTP_1_1); return _willClose || (_responseVersion != HTTP_1_1);
}
int Request::requestBodyLength() const
{
return -1;
} }
std::string Request::requestBodyType() const //------------------------------------------------------------------------------
bool Request::isComplete() const
{ {
return "text/plain"; return _ready_state == DONE || _ready_state == FAILED;
} }
int Request::getBodyData(char*, int maxCount) const //------------------------------------------------------------------------------
bool Request::hasBodyData() const
{ {
return 0; return !_request_media_type.empty();
}
//------------------------------------------------------------------------------
std::string Request::bodyType() const
{
return _request_media_type;
}
//------------------------------------------------------------------------------
size_t Request::bodyLength() const
{
return _request_data.length();
}
//------------------------------------------------------------------------------
size_t Request::getBodyData(char* s, size_t offset, size_t max_count) const
{
size_t bytes_available = _request_data.size() - offset;
size_t bytes_to_read = std::min(bytes_available, max_count);
memcpy(s, _request_data.data() + offset, bytes_to_read);
return bytes_to_read;
} }
} // of namespace HTTP } // of namespace HTTP
} // of namespace simgear } // of namespace simgear
+129 -33
View File
@@ -3,21 +3,85 @@
#include <map> #include <map>
#include <simgear/structure/map.hxx>
#include <simgear/structure/SGReferenced.hxx> #include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx> #include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/math/sg_types.hxx> #include <simgear/math/sg_types.hxx>
#include <boost/function.hpp>
class SGPropertyNode;
namespace simgear namespace simgear
{ {
namespace HTTP namespace HTTP
{ {
class Request : public SGReferenced class Request:
public SGReferenced
{ {
public: public:
typedef boost::function<void(Request*)> Callback;
enum ReadyState
{
UNSENT = 0,
OPENED,
HEADERS_RECEIVED,
LOADING,
DONE,
FAILED
};
virtual ~Request(); virtual ~Request();
/**
*
*/
StringMap& requestHeaders() { return _request_headers; }
const StringMap& requestHeaders() const { return _request_headers; }
std::string& requestHeader(const std::string& key)
{ return _request_headers[key]; }
const std::string requestHeader(const std::string& key) const
{ return _request_headers.get(key); }
/**
* Set the handler to be called when the request successfully completes.
*
* @note If the request is already complete, the handler is called
* immediately.
*/
Request* done(const Callback& cb);
/**
* Set the handler to be called when the request completes or aborts with an
* error.
*
* @note If the request has already failed, the handler is called
* immediately.
*/
Request* fail(const Callback& cb);
/**
* Set the handler to be called when the request either successfully
* completes or fails.
*
* @note If the request is already complete or has already failed, the
* handler is called immediately.
*/
Request* always(const Callback& cb);
/**
* Set the data for the body of the request. The request is automatically
* send using the POST method.
*
* @param data Body data
* @param type Media Type (aka MIME) of the body data
*/
void setBodyData( const std::string& data,
const std::string& type = "text/plain" );
void setBodyData( const SGPropertyNode* data );
virtual void setUrl(const std::string& url); virtual void setUrl(const std::string& url);
virtual std::string method() const virtual std::string method() const
@@ -32,8 +96,8 @@ public:
virtual unsigned short port() const; virtual unsigned short port() const;
virtual std::string query() const; virtual std::string query() const;
virtual string_list requestHeaders() const; StringMap const& responseHeaders() const
virtual std::string header(const std::string& name) const; { return _responseHeaders; }
virtual int responseCode() const virtual int responseCode() const
{ return _responseStatus; } { return _responseStatus; }
@@ -41,26 +105,30 @@ public:
virtual std::string responseReason() const virtual std::string responseReason() const
{ return _responseReason; } { return _responseReason; }
void setResponseLength(unsigned int l); void setResponseLength(unsigned int l);
virtual unsigned int responseLength() const; virtual unsigned int responseLength() const;
/** /**
* Query the size of the request body. -1 (the default value) means no * Check if request contains body data.
* request body
*/ */
virtual int requestBodyLength() const; virtual bool hasBodyData() const;
/**
* Retrieve the request body content type.
*/
virtual std::string bodyType() const;
/**
* Retrieve the size of the request body.
*/
virtual size_t bodyLength() const;
/** /**
* Retrieve the body data bytes. Will be passed the maximum body bytes * Retrieve the body data bytes. Will be passed the maximum body bytes
* to return in the buffer, and must return the actual number * to return in the buffer, and must return the actual number
* of bytes written. * of bytes written.
*/ */
virtual int getBodyData(char* s, int count) const; virtual size_t getBodyData(char* s, size_t offset, size_t max_count) const;
/**
* retrieve the request body content type. Default is text/plain
*/
virtual std::string requestBodyType() const;
/** /**
* running total of body bytes received so far. Can be used * running total of body bytes received so far. Can be used
@@ -80,9 +148,21 @@ public:
HTTPVersion responseVersion() const HTTPVersion responseVersion() const
{ return _responseVersion; } { return _responseVersion; }
static HTTPVersion decodeVersion(const std::string& v); ReadyState readyState() const { return _ready_state; }
/**
* Request aborting this request.
*/
void abort();
/**
* Request aborting this request and specify the reported reaseon for it.
*/
void abort(const std::string& reason);
bool closeAfterComplete() const; bool closeAfterComplete() const;
bool isComplete() const;
protected: protected:
Request(const std::string& url, const std::string method = "GET"); Request(const std::string& url, const std::string method = "GET");
@@ -91,33 +171,49 @@ protected:
virtual void responseHeader(const std::string& key, const std::string& value); virtual void responseHeader(const std::string& key, const std::string& value);
virtual void responseHeadersComplete(); virtual void responseHeadersComplete();
virtual void responseComplete(); virtual void responseComplete();
virtual void failed();
virtual void gotBodyData(const char* s, int n); virtual void gotBodyData(const char* s, int n);
virtual void onDone();
virtual void onFail();
virtual void onAlways();
void setFailure(int code, const std::string& reason);
private: private:
friend class Client; friend class Client;
friend class Connection; friend class Connection;
friend class ContentDecoder;
void processBodyBytes(const char* s, int n); Request(const Request&); // = delete;
void setFailure(int code, const std::string& reason); Request& operator=(const Request&); // = delete;
std::string _method; void processBodyBytes(const char* s, int n);
std::string _url; void setReadyState(ReadyState state);
HTTPVersion _responseVersion;
int _responseStatus; std::string _method;
std::string _responseReason; std::string _url;
unsigned int _responseLength; StringMap _request_headers;
unsigned int _receivedBodyBytes; std::string _request_data;
bool _willClose; std::string _request_media_type;
typedef std::map<std::string, std::string> HeaderDict; HTTPVersion _responseVersion;
HeaderDict _responseHeaders; int _responseStatus;
std::string _responseReason;
StringMap _responseHeaders;
unsigned int _responseLength;
unsigned int _receivedBodyBytes;
Callback _cb_done,
_cb_fail,
_cb_always;
ReadyState _ready_state;
bool _willClose;
}; };
typedef SGSharedPtr<Request> Request_ptr; typedef SGSharedPtr<Request> Request_ptr;
} // of namespace HTTP } // of namespace HTTP
} // of namespace simgear } // of namespace simgear
#endif // of SG_HTTP_REQUEST_HXX #endif // of SG_HTTP_REQUEST_HXX
+29 -14
View File
@@ -28,7 +28,12 @@ typedef std::map<std::string, DAVResource*> DAVResourceMap;
const char* DAV_CACHE_NAME = ".terrasync_cache"; const char* DAV_CACHE_NAME = ".terrasync_cache";
const char* CACHE_VERSION_4_TOKEN = "terrasync-cache-4"; const char* CACHE_VERSION_4_TOKEN = "terrasync-cache-4";
const unsigned int MAX_UPDATE_REPORT_DEPTH = 3;
// important: with the Google servers, setting this higher than '1' causes
// server internal errors (500, the connection is closed). In other words we
// can only specify update report items one level deep at most and no more.
// (the root and its direct children, not NOT grand-children)
const unsigned int MAX_UPDATE_REPORT_DEPTH = 1;
enum LineState enum LineState
{ {
@@ -90,11 +95,15 @@ void SVNDirectory::parseCache()
char versionName[128]; char versionName[128];
LineState lineState = LINESTATE_HREF; LineState lineState = LINESTATE_HREF;
std::ifstream file(p.c_str()); std::ifstream file(p.c_str());
if (!file.is_open()) {
SG_LOG(SG_IO, SG_WARN, "unable to open cache file for reading:" << p);
return;
}
bool doneSelf = false; bool doneSelf = false;
file.getline(href, 1024); file.getline(href, 1024);
if (strcmp(CACHE_VERSION_4_TOKEN, href)) { if (strcmp(CACHE_VERSION_4_TOKEN, href)) {
SG_LOG(SG_IO, SG_WARN, "invalid cache file:" << p.str()); SG_LOG(SG_IO, SG_WARN, "invalid cache file [missing header token]:" << p << " '" << href << "'");
return; return;
} }
@@ -128,8 +137,11 @@ void SVNDirectory::parseCache()
doneSelf = true; doneSelf = true;
} else { } else {
DAVResource* child = addChildDirectory(hrefPtr)->collection(); DAVResource* child = addChildDirectory(hrefPtr)->collection();
child->setVersionName(versionName); string s = strutils::strip(versionName);
} if (!s.empty()) {
child->setVersionName(versionName);
}
} // of done self test
} // of line-state switching } // of line-state switching
} // of file get-line loop } // of file get-line loop
} }
@@ -142,7 +154,7 @@ void SVNDirectory::writeCache()
d.create(0755); d.create(0755);
} }
p.append(DAV_CACHE_NAME); p.append(string(DAV_CACHE_NAME) + ".new");
std::ofstream file(p.c_str(), std::ios::trunc); std::ofstream file(p.c_str(), std::ios::trunc);
// first, cache file version header // first, cache file version header
@@ -159,6 +171,13 @@ void SVNDirectory::writeCache()
file << child->name() << '\n' << child->versionName() << "\n"; file << child->name() << '\n' << child->versionName() << "\n";
} }
} // of child iteration } // of child iteration
file.close();
// approximately atomic delete + rename operation
SGPath cacheName(localPath);
cacheName.append(DAV_CACHE_NAME);
p.rename(cacheName);
} }
void SVNDirectory::setBaseUrl(const string& url) void SVNDirectory::setBaseUrl(const string& url)
@@ -210,6 +229,7 @@ SVNDirectory::addChildDirectory(const std::string& dirName)
DAVCollection* childCol = dav->createChildCollection(dirName); DAVCollection* childCol = dav->createChildCollection(dirName);
SVNDirectory* child = new SVNDirectory(this, childCol); SVNDirectory* child = new SVNDirectory(this, childCol);
childCol->setVersionName(child->cachedRevision());
_children.push_back(child); _children.push_back(child);
writeCache(); writeCache();
return child; return child;
@@ -224,8 +244,6 @@ void SVNDirectory::deleteChildByName(const std::string& nm)
} }
SGPath path = fsDir().file(nm); SGPath path = fsDir().file(nm);
dav->removeChild(child);
delete child;
if (child->isCollection()) { if (child->isCollection()) {
Dir d(path); Dir d(path);
@@ -241,15 +259,13 @@ void SVNDirectory::deleteChildByName(const std::string& nm)
} else { } else {
path.remove(); path.remove();
} }
dav->removeChild(child);
delete child;
writeCache(); writeCache();
} }
void SVNDirectory::requestFailed(HTTP::Request *req)
{
SG_LOG(SG_IO, SG_WARN, "Request failed for:" << req->url());
}
bool SVNDirectory::isDoingSync() const bool SVNDirectory::isDoingSync() const
{ {
if (_doingUpdateReport) { if (_doingUpdateReport) {
@@ -303,7 +319,6 @@ void SVNDirectory::mergeUpdateReportDetails(unsigned int depth,
Dir d(localPath); Dir d(localPath);
if (depth >= MAX_UPDATE_REPORT_DEPTH) { if (depth >= MAX_UPDATE_REPORT_DEPTH) {
std::cerr << localPath << "exceeded MAX_UPDATE_REPORT_DEPTH, cleaning" << std::endl;
d.removeChildren(); d.removeChildren();
return; return;
} }
+1 -5
View File
@@ -51,11 +51,7 @@ public:
// init from a collection // init from a collection
SVNDirectory(SVNDirectory* pr, DAVCollection* col); SVNDirectory(SVNDirectory* pr, DAVCollection* col);
// void update();
// void gotResource(HTTP::Request* get, const std::string& etag);
void requestFailed(HTTP::Request* req);
void beginUpdateReport(); void beginUpdateReport();
void updateReportComplete(); void updateReportComplete();
+64 -41
View File
@@ -16,6 +16,10 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <simgear_config.h>
#endif
#include "SVNReportParser.hxx" #include "SVNReportParser.hxx"
#include <iostream> #include <iostream>
@@ -30,11 +34,16 @@
#include "simgear/misc/sg_path.hxx" #include "simgear/misc/sg_path.hxx"
#include "simgear/misc/sg_dir.hxx" #include "simgear/misc/sg_dir.hxx"
#include "simgear/debug/logstream.hxx" #include "simgear/debug/logstream.hxx"
#include "simgear/xml/xmlparse.h"
#include "simgear/xml/easyxml.hxx" #include "simgear/xml/easyxml.hxx"
#include "simgear/misc/strutils.hxx" #include "simgear/misc/strutils.hxx"
#include "simgear/package/md5.h" #include "simgear/package/md5.h"
#ifdef SYSTEM_EXPAT
# include <expat.h>
#else
# include "sg_expat.h"
#endif
#include "SVNDirectory.hxx" #include "SVNDirectory.hxx"
#include "SVNRepository.hxx" #include "SVNRepository.hxx"
#include "DAVMultiStatus.hxx" #include "DAVMultiStatus.hxx"
@@ -97,12 +106,13 @@ namespace {
const char* SVN_ADD_FILE_TAG = SVN_NS "add-file"; const char* SVN_ADD_FILE_TAG = SVN_NS "add-file";
const char* SVN_TXDELTA_TAG = SVN_NS "txdelta"; const char* SVN_TXDELTA_TAG = SVN_NS "txdelta";
const char* SVN_SET_PROP_TAG = SVN_NS "set-prop"; const char* SVN_SET_PROP_TAG = SVN_NS "set-prop";
const char* SVN_PROP_TAG = SVN_NS "prop";
const char* SVN_DELETE_ENTRY_TAG = SVN_NS "delete-entry"; const char* SVN_DELETE_ENTRY_TAG = SVN_NS "delete-entry";
const char* SVN_DAV_MD5_CHECKSUM = SUBVERSION_DAV_NS ":md5-checksum"; const char* SVN_DAV_MD5_CHECKSUM = SUBVERSION_DAV_NS ":md5-checksum";
const char* DAV_HREF_TAG = DAV_NS "href"; const char* DAV_HREF_TAG = DAV_NS "href";
const char* DAV_CHECKED_IN_TAG = SVN_NS "checked-in"; const char* DAV_CHECKED_IN_TAG = DAV_NS "checked-in";
const int svn_txdelta_source = 0; const int svn_txdelta_source = 0;
@@ -156,7 +166,7 @@ namespace {
_ptr = p; _ptr = p;
} }
bool apply(std::vector<char>& output, std::istream& source) bool apply(std::vector<unsigned char>& output, std::istream& source)
{ {
unsigned char* pEnd = _ptr + instructionLength; unsigned char* pEnd = _ptr + instructionLength;
unsigned char* newData = pEnd; unsigned char* newData = pEnd;
@@ -164,7 +174,7 @@ namespace {
while (_ptr < pEnd) { while (_ptr < pEnd) {
int op = ((*_ptr >> 6) & 0x3); int op = ((*_ptr >> 6) & 0x3);
if (op >= 3) { if (op >= 3) {
SG_LOG(SG_IO, SG_INFO, "SVNDeltaWindow: bad opcode:" << op); SG_LOG(SG_IO, SG_INFO, "SVNDeltaWindow: bad opcode:" << op);
return false; return false;
} }
@@ -186,12 +196,14 @@ namespace {
} }
if (op == svn_txdelta_target) { if (op == svn_txdelta_target) {
while (length > 0) { // this is inefficent, but ranges can overlap.
output.push_back(output[offset++]); while (length > 0) {
--length; output.push_back(output[offset++]);
} --length;
}
} else if (op == svn_txdelta_new) { } else if (op == svn_txdelta_new) {
output.insert(output.end(), newData, newData + length); output.insert(output.end(), newData, newData + length);
newData += length;
} else if (op == svn_txdelta_source) { } else if (op == svn_txdelta_source) {
source.seekg(offset); source.seekg(offset);
char* sourceBuf = (char*) malloc(length); char* sourceBuf = (char*) malloc(length);
@@ -199,6 +211,9 @@ namespace {
source.read(sourceBuf, length); source.read(sourceBuf, length);
output.insert(output.end(), sourceBuf, sourceBuf + length); output.insert(output.end(), sourceBuf, sourceBuf + length);
free(sourceBuf); free(sourceBuf);
} else {
SG_LOG(SG_IO, SG_WARN, "bad opcode logic");
return false;
} }
} // of instruction loop } // of instruction loop
@@ -260,12 +275,12 @@ public:
string fileName(attrs.getValue("name")); string fileName(attrs.getValue("name"));
SGPath filePath(Dir(currentPath).file(fileName)); SGPath filePath(Dir(currentPath).file(fileName));
currentPath = filePath; currentPath = filePath;
DAVResource* res = currentDir->collection()->childWithName(fileName); if (!filePath.exists()) {
if (!res || !filePath.exists()) { fail(SVNRepository::SVN_ERROR_FILE_NOT_FOUND);
// set error condition return;
} }
inFile = true; inFile = true;
} else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) { } else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) {
string dirName(attrs.getValue("name")); string dirName(attrs.getValue("name"));
@@ -293,10 +308,12 @@ public:
} else if (!strcmp(name, SVN_DELETE_ENTRY_TAG)) { } else if (!strcmp(name, SVN_DELETE_ENTRY_TAG)) {
string entryName(attrs.getValue("name")); string entryName(attrs.getValue("name"));
deleteEntry(entryName); deleteEntry(entryName);
} else if (!strcmp(name, DAV_CHECKED_IN_TAG) || !strcmp(name, DAV_HREF_TAG)) { } else if (!strcmp(name, DAV_CHECKED_IN_TAG) ||
!strcmp(name, DAV_HREF_TAG) ||
!strcmp(name, SVN_PROP_TAG)) {
// don't warn on these ones // don't warn on these ones
} else { } else {
//std::cerr << "unhandled element:" << name << std::endl; //SG_LOG(SG_IO, SG_WARN, "SVNReportParser: unhandled tag:" << name);
} }
} // of startElement } // of startElement
@@ -322,11 +339,12 @@ public:
bool decodeTextDelta(const SGPath& outputPath) bool decodeTextDelta(const SGPath& outputPath)
{ {
string decoded = strutils::decodeBase64(txDeltaData); std::vector<unsigned char> output, decoded;
strutils::decodeBase64(txDeltaData, decoded);
size_t bytesToDecode = decoded.size(); size_t bytesToDecode = decoded.size();
std::vector<char> output;
unsigned char* p = (unsigned char*) decoded.data(); unsigned char* p = decoded.data();
if (memcmp(decoded.data(), "SVN\0", DELTA_HEADER_SIZE) != 0) { if (memcmp(p, "SVN\0", DELTA_HEADER_SIZE) != 0) {
return false; // bad header return false; // bad header
} }
@@ -336,6 +354,11 @@ public:
source.open(outputPath.c_str(), std::ios::in | std::ios::binary); source.open(outputPath.c_str(), std::ios::in | std::ios::binary);
while (bytesToDecode > 0) { while (bytesToDecode > 0) {
if (!SVNDeltaWindow::isWindowComplete(p, bytesToDecode)) {
SG_LOG(SG_IO, SG_WARN, "SVN txdelta broken window");
return false;
}
SVNDeltaWindow window(p); SVNDeltaWindow window(p);
assert(bytesToDecode >= window.size()); assert(bytesToDecode >= window.size());
window.apply(output, source); window.apply(output, source);
@@ -344,18 +367,20 @@ public:
} }
source.close(); source.close();
std::ofstream f; std::ofstream f;
f.open(outputPath.c_str(), f.open(outputPath.c_str(),
std::ios::out | std::ios::trunc | std::ios::binary); std::ios::out | std::ios::trunc | std::ios::binary);
f.write(output.data(), output.size()); f.write((char*) output.data(), output.size());
// compute MD5 while we have the file in memory // compute MD5 while we have the file in memory
MD5_CTX md5; memset(&md5Context, 0, sizeof(SG_MD5_CTX));
memset(&md5, 0, sizeof(MD5_CTX)); SG_MD5Init(&md5Context);
MD5Init(&md5); SG_MD5Update(&md5Context, (unsigned char*) output.data(), output.size());
MD5Update(&md5, (unsigned char*) output.data(), output.size()); unsigned char digest[MD5_DIGEST_LENGTH];
MD5Final(&md5); SG_MD5Final(digest, &md5Context);
decodedFileMd5 = strutils::encodeHex(md5.digest, 16); decodedFileMd5 = strutils::encodeHex(digest, MD5_DIGEST_LENGTH);
return true; return true;
} }
@@ -367,16 +392,15 @@ public:
assert(tagStack.back() == name); assert(tagStack.back() == name);
tagStack.pop_back(); tagStack.pop_back();
if (!strcmp(name, SVN_TXDELTA_TAG)) { if (!strcmp(name, SVN_TXDELTA_TAG)) {
if (!decodeTextDelta(currentPath)) { if (!decodeTextDelta(currentPath)) {
fail(SVNRepository::SVN_ERROR_TXDELTA); fail(SVNRepository::SVN_ERROR_TXDELTA);
} }
} else if (!strcmp(name, SVN_ADD_FILE_TAG)) { } else if (!strcmp(name, SVN_ADD_FILE_TAG)) {
finishFile(currentDir->addChildFile(currentPath.file())); finishFile(currentPath);
} else if (!strcmp(name, SVN_OPEN_FILE_TAG)) { } else if (!strcmp(name, SVN_OPEN_FILE_TAG)) {
DAVResource* res = currentDir->collection()->childWithName(currentPath.file()); finishFile(currentPath);
assert(res);
finishFile(res);
} else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) { } else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) {
// pop directory // pop directory
currentPath = currentPath.dir(); currentPath = currentPath.dir();
@@ -399,23 +423,21 @@ public:
fail(SVNRepository::SVN_ERROR_CHECKSUM); fail(SVNRepository::SVN_ERROR_CHECKSUM);
} }
} else if (!strcmp(name, SVN_OPEN_DIRECTORY_TAG)) { } else if (!strcmp(name, SVN_OPEN_DIRECTORY_TAG)) {
currentDir->updateReportComplete();
if (currentDir->parent()) { if (currentDir->parent()) {
// pop the collection stack // pop the collection stack
currentDir = currentDir->parent(); currentDir = currentDir->parent();
} }
currentDir->updateReportComplete();
currentPath = currentDir->fsPath(); currentPath = currentDir->fsPath();
} else { } else {
// std::cout << "element:" << name; // std::cout << "element:" << name;
} }
} }
void finishFile(DAVResource* res) void finishFile(const SGPath& path)
{ {
res->setVersionName(currentVersionName); currentPath = path.dir();
res->setMD5(md5Sum);
currentPath = currentPath.dir();
inFile = false; inFile = false;
} }
@@ -426,11 +448,11 @@ public:
} }
if (tagStack.back() == SVN_SET_PROP_TAG) { if (tagStack.back() == SVN_SET_PROP_TAG) {
setPropValue += string(s, length); setPropValue.append(s, length);
} else if (tagStack.back() == SVN_TXDELTA_TAG) { } else if (tagStack.back() == SVN_TXDELTA_TAG) {
txDeltaData += string(s, length); txDeltaData.append(s, length);
} else if (tagStack.back() == SVN_DAV_MD5_CHECKSUM) { } else if (tagStack.back() == SVN_DAV_MD5_CHECKSUM) {
md5Sum += string(s, length); md5Sum.append(s, length);
} }
} }
@@ -467,6 +489,7 @@ public:
bool inFile; bool inFile;
unsigned int revision; unsigned int revision;
SG_MD5_CTX md5Context;
string md5Sum, decodedFileMd5; string md5Sum, decodedFileMd5;
std::string setPropName, setPropValue; std::string setPropName, setPropValue;
}; };
@@ -538,7 +561,7 @@ SVNReportParser::innerParseXML(const char* data, int size)
XML_ParserFree(_d->xmlParser); XML_ParserFree(_d->xmlParser);
_d->parserInited = false; _d->parserInited = false;
} }
return _d->status; return _d->status;
} }
+50 -82
View File
@@ -73,7 +73,8 @@ public:
void updateFailed(HTTP::Request* req, SVNRepository::ResultCode err) void updateFailed(HTTP::Request* req, SVNRepository::ResultCode err)
{ {
SG_LOG(SG_IO, SG_WARN, "SVN: failed to update from:" << req->url()); SG_LOG(SG_IO, SG_WARN, "SVN: failed to update from:" << req->url()
<< "\n(repository:" << p->baseUrl() << ")");
isUpdating = false; isUpdating = false;
status = err; status = err;
} }
@@ -119,40 +120,9 @@ namespace { // anonmouse
Request(repo->baseUrl, "PROPFIND"), Request(repo->baseUrl, "PROPFIND"),
_repo(repo) _repo(repo)
{ {
} requestHeader("Depth") = "0";
setBodyData( PROPFIND_REQUEST_BODY,
virtual string_list requestHeaders() const "application/xml; charset=\"utf-8\"" );
{
string_list r;
r.push_back("Depth");
return r;
}
virtual string header(const string& name) const
{
if (name == "Depth") {
return "0";
}
return string();
}
virtual string requestBodyType() const
{
return "application/xml; charset=\"utf-8\"";
}
virtual int requestBodyLength() const
{
return strlen(PROPFIND_REQUEST_BODY);
}
virtual int getBodyData(char* buf, int count) const
{
int bodyLen = strlen(PROPFIND_REQUEST_BODY);
assert(count >= bodyLen);
memcpy(buf, PROPFIND_REQUEST_BODY, bodyLen);
return bodyLen;
} }
protected: protected:
@@ -166,14 +136,19 @@ namespace { // anonmouse
SG_LOG(SG_IO, SG_WARN, "request for:" << url() << SG_LOG(SG_IO, SG_WARN, "request for:" << url() <<
" return code " << responseCode()); " return code " << responseCode());
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET); _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
_repo = NULL;
} }
} }
virtual void responseComplete() virtual void onDone()
{ {
if (responseCode() == 207) { if (responseCode() == 207) {
_davStatus.finishParse(); _davStatus.finishParse();
_repo->propFindComplete(this, (DAVCollection*) _davStatus.resource()); if (_davStatus.isValid()) {
_repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
} else {
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
}
} }
} }
@@ -184,69 +159,58 @@ namespace { // anonmouse
} }
_davStatus.parseXML(s, n); _davStatus.parseXML(s, n);
} }
virtual void onFail()
{
HTTP::Request::onFail();
if (_repo) {
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
_repo = NULL;
}
}
private: private:
SVNRepoPrivate* _repo; SVNRepoPrivate* _repo;
DAVMultiStatus _davStatus; DAVMultiStatus _davStatus;
}; };
class UpdateReportRequest : public HTTP::Request class UpdateReportRequest:
public HTTP::Request
{ {
public: public:
UpdateReportRequest(SVNRepoPrivate* repo, UpdateReportRequest(SVNRepoPrivate* repo,
const std::string& aVersionName, const std::string& aVersionName,
bool startEmpty) : bool startEmpty) :
HTTP::Request("", "REPORT"), HTTP::Request("", "REPORT"),
_requestSent(0),
_parser(repo->p), _parser(repo->p),
_repo(repo), _repo(repo),
_failed(false) _failed(false)
{ {
setUrl(repo->vccUrl); setUrl(repo->vccUrl);
std::string request =
_request =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<S:update-report send-all=\"true\" xmlns:S=\"svn:\">\n" "<S:update-report send-all=\"true\" xmlns:S=\"svn:\">\n"
"<S:src-path>" + repo->baseUrl + "</S:src-path>\n" "<S:src-path>" + repo->baseUrl + "</S:src-path>\n"
"<S:depth>unknown</S:depth>\n"; "<S:depth>unknown</S:depth>\n"
"<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
_request += "<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n"; if( !startEmpty )
{
if (!startEmpty) { string_list entries;
string_list entries; _repo->rootCollection->mergeUpdateReportDetails(0, entries);
_repo->rootCollection->mergeUpdateReportDetails(0, entries); BOOST_FOREACH(string e, entries)
BOOST_FOREACH(string e, entries) { {
_request += e + "\n"; request += e + "\n";
} }
} }
_request += "</S:update-report>"; request += "</S:update-report>";
}
virtual string requestBodyType() const setBodyData(request, "application/xml; charset=\"utf-8\"");
{
return "application/xml; charset=\"utf-8\"";
}
virtual int requestBodyLength() const
{
return _request.size();
}
virtual int getBodyData(char* buf, int count) const
{
int len = std::min(count, requestBodyLength() - _requestSent);
memcpy(buf, _request.c_str() + _requestSent, len);
_requestSent += len;
return len;
} }
protected: protected:
virtual void responseHeadersComplete() virtual void onDone()
{
}
virtual void responseComplete()
{ {
if (_failed) { if (_failed) {
return; return;
@@ -265,8 +229,8 @@ protected:
_failed = true; _failed = true;
} else { } else {
SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() << SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() <<
" return code " << responseCode()); " got HTTP status " << responseCode());
_repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET); _repo->updateFailed(this, SVNRepository::SVN_ERROR_HTTP);
_failed = true; _failed = true;
} }
} }
@@ -281,20 +245,24 @@ protected:
return; return;
} }
//cout << "body data:" << string(s, n) << endl;
SVNRepository::ResultCode err = _parser.parseXML(s, n); SVNRepository::ResultCode err = _parser.parseXML(s, n);
if (err) { if (err) {
_failed = true; _failed = true;
SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() << SG_LOG(SG_IO, SG_WARN, this << ": SVN: request for:" << url() << " failed:" << err);
" XML parse failed");
_repo->updateFailed(this, err); _repo->updateFailed(this, err);
_repo = NULL;
} }
} }
virtual void onFail()
{
HTTP::Request::onFail();
if (_repo) {
_repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
_repo = NULL;
}
}
private: private:
string _request;
mutable int _requestSent;
SVNReportParser _parser; SVNReportParser _parser;
SVNRepoPrivate* _repo; SVNRepoPrivate* _repo;
bool _failed; bool _failed;
+3 -1
View File
@@ -61,7 +61,9 @@ public:
SVN_ERROR_XML, SVN_ERROR_XML,
SVN_ERROR_TXDELTA, SVN_ERROR_TXDELTA,
SVN_ERROR_IO, SVN_ERROR_IO,
SVN_ERROR_CHECKSUM SVN_ERROR_CHECKSUM,
SVN_ERROR_FILE_NOT_FOUND,
SVN_ERROR_HTTP
}; };
ResultCode failure() const; ResultCode failure() const;
+4 -2
View File
@@ -32,10 +32,12 @@ int main(int argc, char* argv[])
httpClient = &cl; httpClient = &cl;
SGPath p("/Users/jmt/Desktop/scenemodels"); SGPath p("/Users/jmt/Desktop/traffic");
SVNRepository airports(p, &cl); SVNRepository airports(p, &cl);
// airports.setBaseUrl("http://svn.goneabitbursar.com/testproject1"); // airports.setBaseUrl("http://svn.goneabitbursar.com/testproject1");
airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Models"); // airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Models");
airports.setBaseUrl("http://fgfs.goneabitbursar.com/fgfsai/trunk/AI/Traffic");
// airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Airports"); // airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Airports");
airports.update(); airports.update();
+2 -27
View File
@@ -48,35 +48,11 @@ public:
} }
string key = h.substr(0, colonPos); string key = h.substr(0, colonPos);
_headers[key] = h.substr(colonPos + 1); requestHeader(key) = h.substr(colonPos + 1);
} }
virtual string_list requestHeaders() const
{
string_list r;
std::map<string, string>::const_iterator it;
for (it = _headers.begin(); it != _headers.end(); ++it) {
r.push_back(it->first);
}
return r;
}
virtual string header(const string& name) const
{
std::map<string, string>::const_iterator it = _headers.find(name);
if (it == _headers.end()) {
return string();
}
return it->second;
}
protected: protected:
virtual void responseHeadersComplete() virtual void onDone()
{
}
virtual void responseComplete()
{ {
_complete = true; _complete = true;
} }
@@ -88,7 +64,6 @@ protected:
private: private:
bool _complete; bool _complete;
SGFile* _file; SGFile* _file;
std::map<string, string> _headers;
}; };
int main(int argc, char* argv[]) int main(int argc, char* argv[])
+24 -3
View File
@@ -454,6 +454,23 @@ bool Socket::open ( bool stream )
#endif #endif
} }
#ifdef SO_NOSIGPIPE
// Do not generate SIGPIPE signal (which immediately terminates the program),
// instead ::send() will return -1 and errno will be set to EPIPE.
// SO_NOSIGPIPE should be available on Mac/BSD systems, but is not available
// within Posix/Linux.
// This only works for calls to ::send() but not for ::write():
// http://freebsd.1045724.n5.nabble.com/is-setsockopt-SO-NOSIGPIPE-work-tp4011054p4011055.html
int set = 1;
setsockopt(handle, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(set));
#endif
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif
// TODO supress SIGPIPE if neither SO_NOSIGPIPE nor MSG_NOSIGNAL is available
// http://krokisplace.blogspot.co.at/2010/02/suppressing-sigpipe-in-library.html
return (handle != -1); return (handle != -1);
} }
@@ -592,7 +609,7 @@ int Socket::connect ( IPAddress* addr )
int Socket::send (const void * buffer, int size, int flags) int Socket::send (const void * buffer, int size, int flags)
{ {
assert ( handle != -1 ) ; assert ( handle != -1 ) ;
return ::send (handle, (const char*)buffer, size, flags); return ::send (handle, (const char*)buffer, size, flags | MSG_NOSIGNAL);
} }
@@ -600,8 +617,12 @@ int Socket::sendto ( const void * buffer, int size,
int flags, const IPAddress* to ) int flags, const IPAddress* to )
{ {
assert ( handle != -1 ) ; assert ( handle != -1 ) ;
return ::sendto(handle,(const char*)buffer,size,flags, return ::sendto( handle,
(const sockaddr*) to->getAddr(), to->getAddrLen()); (const char*)buffer,
size,
flags | MSG_NOSIGNAL,
(const sockaddr*)to->getAddr(),
to->getAddrLen() );
} }
+23 -22
View File
@@ -43,6 +43,7 @@
#include <simgear/bucket/newbucket.hxx> #include <simgear/bucket/newbucket.hxx>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/math/SGGeometry.hxx> #include <simgear/math/SGGeometry.hxx>
#include <simgear/structure/exception.hxx>
#include "lowlevel.hxx" #include "lowlevel.hxx"
#include "sg_binobj.hxx" #include "sg_binobj.hxx"
@@ -319,15 +320,18 @@ void SGBinObject::read_object( gzFile fp,
} }
if ( sgReadError() ) { if ( sgReadError() ) {
cout << "We detected an error reading object properties" << endl; throw sg_exception("Error reading object properties");
return; }
size_t indexCount = std::bitset<32>(idx_mask).count();
if (indexCount == 0) {
throw sg_exception("object index mask has no bits set");
} }
for ( j = 0; j < nelements; ++j ) { for ( j = 0; j < nelements; ++j ) {
sgReadUInt( fp, &nbytes ); sgReadUInt( fp, &nbytes );
if ( sgReadError() ) { if ( sgReadError() ) {
cout << "We detected an error reading element size for :" << j << endl; throw sg_exception("Error reading element size");
return;
} }
buf.resize( nbytes ); buf.resize( nbytes );
@@ -335,8 +339,7 @@ void SGBinObject::read_object( gzFile fp,
sgReadBytes( fp, nbytes, ptr ); sgReadBytes( fp, nbytes, ptr );
if ( sgReadError() ) { if ( sgReadError() ) {
cout << "We detected an error reading object element:" << j << "bytes="<< nbytes << endl; throw sg_exception("Error reading element bytes");
return;
} }
int_list vs; int_list vs;
@@ -405,7 +408,7 @@ bool SGBinObject::read_bin( const string& file ) {
SG_LOG( SG_EVENT, SG_ALERT, SG_LOG( SG_EVENT, SG_ALERT,
"ERROR: opening " << file << " or " << filegz << " for reading!"); "ERROR: opening " << file << " or " << filegz << " for reading!");
return false; throw sg_io_exception("Error opening for reading (and .gz)", sg_location(file));
} }
} }
@@ -423,9 +426,7 @@ bool SGBinObject::read_bin( const string& file ) {
} else { } else {
// close the file before we return // close the file before we return
gzclose(fp); gzclose(fp);
SG_LOG( SG_EVENT, SG_ALERT, throw sg_io_exception("Bad BTG magic/version", sg_location(file));
"ERROR: " << file << "has bad header");
return false;
} }
// read creation time // read creation time
@@ -462,8 +463,7 @@ bool SGBinObject::read_bin( const string& file ) {
//cout << "Total objects to read = " << nobjects << endl; //cout << "Total objects to read = " << nobjects << endl;
if ( sgReadError() ) { if ( sgReadError() ) {
cout << "Error while reading header of file " << file << "(.gz)" << endl; throw sg_io_exception("Error reading BTG file header", sg_location(file));
return false;
} }
// read in objects // read in objects
@@ -613,19 +613,13 @@ bool SGBinObject::read_bin( const string& file ) {
} }
if ( sgReadError() ) { if ( sgReadError() ) {
cout << "Error while reading object:" << i << " in file " << file << "(.gz)" << endl; throw sg_io_exception("Error while reading object", sg_location(file, i));
return false;
} }
} }
// close the file // close the file
gzclose(fp); gzclose(fp);
if ( sgReadError() ) {
cout << "Error while reading file " << file << "(.gz)" << endl;
return false;
}
return true; return true;
} }
@@ -665,7 +659,7 @@ void SGBinObject::write_objects(gzFile fp, int type, const group_list& verts,
if (verts.empty()) { if (verts.empty()) {
return; return;
} }
unsigned int start = 0, end = 1; unsigned int start = 0, end = 1;
string m; string m;
int_list emptyList; int_list emptyList;
@@ -688,6 +682,13 @@ void SGBinObject::write_objects(gzFile fp, int type, const group_list& verts,
if ( !normals.empty() && !normals.front().empty()) idx_mask |= SG_IDX_NORMALS; if ( !normals.empty() && !normals.front().empty()) idx_mask |= SG_IDX_NORMALS;
if ( !colors.empty() && !colors.front().empty()) idx_mask |= SG_IDX_COLORS; if ( !colors.empty() && !colors.front().empty()) idx_mask |= SG_IDX_COLORS;
if ( !texCoords.empty() && !texCoords.front().empty()) idx_mask |= SG_IDX_TEXCOORDS; if ( !texCoords.empty() && !texCoords.front().empty()) idx_mask |= SG_IDX_TEXCOORDS;
if (idx_mask == 0) {
SG_LOG(SG_IO, SG_ALERT, "SGBinObject::write_objects: object with material:"
<< m << "has no indices set");
}
sgWriteChar( fp, (char)SG_INDEX_TYPES ); // property sgWriteChar( fp, (char)SG_INDEX_TYPES ); // property
sgWriteUInt( fp, 1 ); // nbytes sgWriteUInt( fp, 1 ); // nbytes
sgWriteChar( fp, idx_mask ); sgWriteChar( fp, idx_mask );
@@ -934,7 +935,7 @@ bool SGBinObject::write_ascii( const string& base, const string& name,
fprintf(fp, "\n"); fprintf(fp, "\n");
// dump individual triangles if they exist // dump individual triangles if they exist
if ( tris_v.size() > 0 ) { if ( ! tris_v.empty() ) {
fprintf(fp, "# triangle groups\n"); fprintf(fp, "# triangle groups\n");
int start = 0; int start = 0;
@@ -982,7 +983,7 @@ bool SGBinObject::write_ascii( const string& base, const string& name,
} }
// dump triangle groups // dump triangle groups
if ( strips_v.size() > 0 ) { if ( ! strips_v.empty() ) {
fprintf(fp, "# triangle strips\n"); fprintf(fp, "# triangle strips\n");
int start = 0; int start = 0;
+2
View File
@@ -123,6 +123,8 @@ public:
void addChannel(NetChannel* channel); void addChannel(NetChannel* channel);
void removeChannel(NetChannel* channel); void removeChannel(NetChannel* channel);
bool hasChannels() const { return !channels.empty(); }
bool poll(unsigned int timeout = 0); bool poll(unsigned int timeout = 0);
void loop(unsigned int timeout = 0); void loop(unsigned int timeout = 0);
}; };
+2
View File
@@ -70,6 +70,8 @@ class NetChat : public NetBufferChannel
{ {
std::string terminator; std::string terminator;
int bytesToCollect; int bytesToCollect;
protected:
virtual void handleBufferRead (NetBuffer& buffer) ; virtual void handleBufferRead (NetBuffer& buffer) ;
public: public:
+15 -2
View File
@@ -103,9 +103,14 @@ int SGSocketUDP::read( char *buf, int length ) {
return 0; return 0;
} }
if (length <= 0) {
return 0;
}
int result; int result;
// prevent buffer overflow
int maxsize = std::min(length - 1, SG_IO_MAX_MSG_SIZE);
if ( (result = sock.recv(buf, SG_IO_MAX_MSG_SIZE, 0)) >= 0 ) { if ( (result = sock.recv(buf, maxsize, 0)) >= 0 ) {
buf[result] = '\0'; buf[result] = '\0';
// printf("msg received = %s\n", buf); // printf("msg received = %s\n", buf);
} }
@@ -120,10 +125,16 @@ int SGSocketUDP::readline( char *buf, int length ) {
return 0; return 0;
} }
if (length <= 0) {
return 0;
}
// cout << "sock = " << sock << endl; // cout << "sock = " << sock << endl;
char *buf_ptr = save_buf + save_len; char *buf_ptr = save_buf + save_len;
int result = sock.recv(buf_ptr, SG_IO_MAX_MSG_SIZE, 0); // prevent buffer overflow (size of save_buf is 2 * SG_IO_MAX_MSG_SIZE)
int maxsize = save_len < SG_IO_MAX_MSG_SIZE ?
SG_IO_MAX_MSG_SIZE : 2 * SG_IO_MAX_MSG_SIZE - save_len;
int result = sock.recv(buf_ptr, maxsize, 0);
// printf("msg received = %s\n", buf); // printf("msg received = %s\n", buf);
save_len += result; save_len += result;
@@ -142,6 +153,8 @@ int SGSocketUDP::readline( char *buf, int length ) {
// we found an end of line // we found an end of line
// copy to external buffer // copy to external buffer
// prevent buffer overflow
result = std::min(result,length - 1);
strncpy( buf, save_buf, result ); strncpy( buf, save_buf, result );
buf[result] = '\0'; buf[result] = '\0';
// cout << "sg_socket line = " << buf << endl; // cout << "sg_socket line = " << buf << endl;
+11 -38
View File
@@ -53,48 +53,23 @@ public:
bool complete; bool complete;
bool failed; bool failed;
string bodyData; string bodyData;
string bodyContentType;
TestRequest(const std::string& url, const std::string method = "GET") : TestRequest(const std::string& url, const std::string method = "GET") :
HTTP::Request(url, method), HTTP::Request(url, method),
complete(false) complete(false)
{ {
bodyContentType = "text/plain";
} }
std::map<string, string> sendHeaders;
std::map<string, string> headers; std::map<string, string> headers;
protected: protected:
string_list requestHeaders() const
{
string_list r;
std::map<string, string>::const_iterator it;
for (it = sendHeaders.begin(); it != sendHeaders.end(); ++it) {
r.push_back(it->first);
}
return r;
}
string header(const string& name) const virtual void onDone()
{
std::map<string, string>::const_iterator it = sendHeaders.find(name);
if (it == sendHeaders.end()) {
return string();
}
return it->second;
}
virtual void responseHeadersComplete()
{
}
virtual void responseComplete()
{ {
complete = true; complete = true;
} }
virtual void failure() virtual void onFail()
{ {
failed = true; failed = true;
} }
@@ -105,11 +80,6 @@ protected:
bodyData += string(s, n); bodyData += string(s, n);
} }
virtual std::string requestBodyType() const
{
return bodyContentType;
}
virtual void responseHeader(const string& header, const string& value) virtual void responseHeader(const string& header, const string& value)
{ {
headers[header] = value; headers[header] = value;
@@ -477,7 +447,9 @@ int main(int argc, char* argv[])
{ {
HTTP::Client cl; HTTP::Client cl;
// force all requests to use the same connection for this test
cl.setMaxConnections(1);
// test URL parsing // test URL parsing
TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar"); TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
COMPARE(tr1->scheme(), "http"); COMPARE(tr1->scheme(), "http");
@@ -533,8 +505,8 @@ int main(int argc, char* argv[])
{ {
TestRequest* tr = new TestRequest("http://localhost:2000/test_headers"); TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
HTTP::Request_ptr own(tr); HTTP::Request_ptr own(tr);
tr->sendHeaders["X-Foo"] = "Bar"; tr->requestHeader("X-Foo") = "Bar";
tr->sendHeaders["X-AnotherHeader"] = "A longer value"; tr->requestHeader("X-AnotherHeader") = "A longer value";
cl.makeRequest(tr); cl.makeRequest(tr);
waitForComplete(&cl, tr); waitForComplete(&cl, tr);
@@ -657,6 +629,7 @@ int main(int argc, char* argv[])
cout << "testing HTTP 1.1 pipelineing" << endl; cout << "testing HTTP 1.1 pipelineing" << endl;
{ {
cl.setProxy("", 80); cl.setProxy("", 80);
TestRequest* tr = new TestRequest("http://localhost:2000/test1"); TestRequest* tr = new TestRequest("http://localhost:2000/test1");
HTTP::Request_ptr own(tr); HTTP::Request_ptr own(tr);
@@ -718,7 +691,7 @@ int main(int argc, char* argv[])
{ {
cout << "POST" << endl; cout << "POST" << endl;
TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST"); TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
tr->bodyContentType = "application/x-www-form-urlencoded"; tr->setBodyData("", "application/x-www-form-urlencoded");
HTTP::Request_ptr own(tr); HTTP::Request_ptr own(tr);
cl.makeRequest(tr); cl.makeRequest(tr);
+1
View File
@@ -3,6 +3,7 @@ include (SimGearComponent)
set(HEADERS set(HEADERS
CSSBorder.hxx CSSBorder.hxx
ListDiff.hxx
ResourceManager.hxx ResourceManager.hxx
interpolator.hxx interpolator.hxx
make_new.hxx make_new.hxx
+2 -2
View File
@@ -58,7 +58,7 @@ namespace simgear
{ {
ret.val[i] = offsets.val[i]; ret.val[i] = offsets.val[i];
if( !types.rel[i] ) if( !types.rel[i] )
ret.val[i] /= (i & 1) ? dim.height() : dim.width(); ret.val[i] /= (i & 1) ? dim.width() : dim.height();
} }
return ret; return ret;
@@ -75,7 +75,7 @@ namespace simgear
{ {
ret.val[i] = offsets.val[i]; ret.val[i] = offsets.val[i];
if( types.rel[i] ) if( types.rel[i] )
ret.val[i] *= (i & 1) ? dim.height() : dim.width(); ret.val[i] *= (i & 1) ? dim.width() : dim.height();
} }
return ret; return ret;
+14 -7
View File
@@ -55,14 +55,21 @@ int main (int ac, char ** av)
COMPARE(o.b, 15); COMPARE(o.b, 15);
COMPARE(o.l, 20); COMPARE(o.l, 20);
b = CSSBorder::parse("5% 10% 15% 20%"); b = CSSBorder::parse("5 10 15 20");
o = b.getAbsOffsets(SGRect<int>(0,0,200,200)); o = b.getRelOffsets(SGRect<int>(0,0,100,200));
COMPARE(o.t, 10); COMPARE(o.t, 0.025);
COMPARE(o.r, 20); COMPARE(o.r, 0.1);
COMPARE(o.b, 30); COMPARE(o.b, 0.075);
COMPARE(o.l, 40); COMPARE(o.l, 0.2);
o = b.getRelOffsets(SGRect<int>(0,0,200,200)); b = CSSBorder::parse("5% 10% 15% 20%");
o = b.getAbsOffsets(SGRect<int>(0,0,100,200));
COMPARE(o.t, 10);
COMPARE(o.r, 10);
COMPARE(o.b, 30);
COMPARE(o.l, 20);
o = b.getRelOffsets(SGRect<int>(0,0,100,200));
COMPARE(o.t, 0.05); COMPARE(o.t, 0.05);
COMPARE(o.r, 0.1); COMPARE(o.r, 0.1);
COMPARE(o.b, 0.15); COMPARE(o.b, 0.15);
+85
View File
@@ -0,0 +1,85 @@
///@file
/// Compare lists and get differences
///
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_LISTDIFF_HXX_
#define SG_LISTDIFF_HXX_
#include <vector>
#include <boost/function.hpp>
namespace simgear
{
template<class T>
struct ListDiff
{
typedef std::vector<T> List;
typedef boost::function<void (T)> Callback;
/**
* Perform list diff in-place (modifies both lists) and call cb_add for
* every element in new_list not in old_list and cb_remove for every element
* in old_list not in new_list.
*/
static void inplace( List& old_list,
List& new_list,
Callback cb_add,
Callback cb_remove )
{
// Check which elements have been removed. (Removing first and adding
// second should keep the memory usage lower - not for this function, but
// probably for users of this function which use the callbacks to delete
// and create objects)
while( !old_list.empty() )
{
T& old_el = old_list.front();
typename List::iterator new_el =
std::find(new_list.begin(), new_list.end(), old_el);
if( new_el == new_list.end() )
{
if( cb_remove )
cb_remove(old_el);
}
else
{
// Element is in both lists -> just ignore
*new_el = new_list.back();
new_list.pop_back();
}
old_list.front() = old_list.back();
old_list.pop_back();
}
// All remaing elements in new_list have not been in old_list, so call
// the add callback for every element if required.
if( cb_add )
{
for( typename List::iterator it = new_list.begin();
it != new_list.end();
++it )
cb_add(*it);
}
}
};
} // namespace simgear
#endif /* SG_LISTDIFF_HXX_ */
+44 -1
View File
@@ -43,6 +43,30 @@ void test_dir()
cout << temp.path().modTime() << endl; cout << temp.path().modTime() << endl;
} }
SGPath::Permissions validateNone(const SGPath&)
{
SGPath::Permissions p;
p.read = false;
p.write = false;
return p;
}
SGPath::Permissions validateRead(const SGPath&)
{
SGPath::Permissions p;
p.read = true;
p.write = false;
return p;
}
SGPath::Permissions validateWrite(const SGPath&)
{
SGPath::Permissions p;
p.read = false;
p.write = true;
return p;
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
SGPath pa; SGPath pa;
@@ -143,7 +167,26 @@ int main(int argc, char* argv[])
COMPARE(pf.dir(), ""); COMPARE(pf.dir(), "");
COMPARE(pf.lower_extension(), "gz"); COMPARE(pf.lower_extension(), "gz");
COMPARE(pf.complete_lower_extension(), "txt.gz"); COMPARE(pf.complete_lower_extension(), "txt.gz");
COMPARE(pf.canRead(), true);
COMPARE(pf.canWrite(), true);
SGPath pp(&validateNone);
COMPARE(pp.canRead(), false);
COMPARE(pp.canWrite(), false);
pp.append("./test-dir/file.txt");
COMPARE(pp.create_dir(0700), -3);
pp.setPermissonChecker(&validateRead);
COMPARE(pp.canRead(), true);
COMPARE(pp.canWrite(), false);
COMPARE(pp.create_dir(0700), -3);
pp.setPermissonChecker(&validateWrite);
COMPARE(pp.canRead(), false);
COMPARE(pp.canWrite(), true);
test_dir(); test_dir();
cout << "all tests passed OK" << endl; cout << "all tests passed OK" << endl;
+5 -1
View File
@@ -149,7 +149,11 @@ PathList Dir::children(int types, const std::string& nameFilter) const
WIN32_FIND_DATA fData; WIN32_FIND_DATA fData;
HANDLE find = FindFirstFile(search.c_str(), &fData); HANDLE find = FindFirstFile(search.c_str(), &fData);
if (find == INVALID_HANDLE_VALUE) { if (find == INVALID_HANDLE_VALUE) {
SG_LOG(SG_GENERAL, SG_WARN, "Dir::children: FindFirstFile failed:" << _path.str()); int err = GetLastError();
if (err != ERROR_FILE_NOT_FOUND) {
SG_LOG(SG_GENERAL, SG_WARN, "Dir::children: FindFirstFile failed:" <<
_path.str() << " with error:" << err);
}
return result; return result;
} }
+221 -75
View File
@@ -55,6 +55,34 @@ static const char sgSearchPathSep = ';';
static const char sgSearchPathSep = ':'; static const char sgSearchPathSep = ':';
#endif #endif
#ifdef _WIN32
#include <ShlObj.h> // for CSIDL
static SGPath pathForCSIDL(int csidl, const SGPath::PermissonChecker& checker)
{
typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
// lazy open+resolve of shell32
if (!SHGetSpecialFolderPath) {
HINSTANCE shellDll = ::LoadLibrary("shell32");
SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
}
if (!SHGetSpecialFolderPath){
return SGPath();
}
char path[MAX_PATH];
if (SHGetSpecialFolderPath(0, path, csidl, false)) {
return SGPath(path, checker);
}
return SGPath();
}
#endif
// For windows, replace "\" by "/". // For windows, replace "\" by "/".
void void
@@ -75,27 +103,35 @@ SGPath::fix()
// default constructor // default constructor
SGPath::SGPath() SGPath::SGPath(PermissonChecker validator)
: path(""), : path(""),
_permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false),
_cacheEnabled(true) _cacheEnabled(true)
{ {
} }
// create a path based on "path" // create a path based on "path"
SGPath::SGPath( const std::string& p ) SGPath::SGPath( const std::string& p, PermissonChecker validator )
: path(p), : path(p),
_permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false),
_cacheEnabled(true) _cacheEnabled(true)
{ {
fix(); fix();
} }
// create a path based on "path" and a "subpath" // create a path based on "path" and a "subpath"
SGPath::SGPath( const SGPath& p, const std::string& r ) SGPath::SGPath( const SGPath& p,
const std::string& r,
PermissonChecker validator )
: path(p.path), : path(p.path),
_permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false),
_cacheEnabled(p._cacheEnabled) _cacheEnabled(p._cacheEnabled)
{ {
append(r); append(r);
@@ -104,8 +140,12 @@ SGPath::SGPath( const SGPath& p, const std::string& r )
SGPath::SGPath(const SGPath& p) : SGPath::SGPath(const SGPath& p) :
path(p.path), path(p.path),
_permission_checker(p._permission_checker),
_cached(p._cached), _cached(p._cached),
_rwCached(p._rwCached),
_cacheEnabled(p._cacheEnabled), _cacheEnabled(p._cacheEnabled),
_canRead(p._canRead),
_canWrite(p._canWrite),
_exists(p._exists), _exists(p._exists),
_isDir(p._isDir), _isDir(p._isDir),
_isFile(p._isFile), _isFile(p._isFile),
@@ -116,8 +156,12 @@ SGPath::SGPath(const SGPath& p) :
SGPath& SGPath::operator=(const SGPath& p) SGPath& SGPath::operator=(const SGPath& p)
{ {
path = p.path; path = p.path;
_permission_checker = p._permission_checker,
_cached = p._cached; _cached = p._cached;
_rwCached = p._rwCached;
_cacheEnabled = p._cacheEnabled; _cacheEnabled = p._cacheEnabled;
_canRead = p._canRead;
_canWrite = p._canWrite;
_exists = p._exists; _exists = p._exists;
_isDir = p._isDir; _isDir = p._isDir;
_isFile = p._isFile; _isFile = p._isFile;
@@ -135,16 +179,31 @@ void SGPath::set( const string& p ) {
path = p; path = p;
fix(); fix();
_cached = false; _cached = false;
_rwCached = false;
} }
//------------------------------------------------------------------------------
void SGPath::setPermissonChecker(PermissonChecker validator)
{
_permission_checker = validator;
_rwCached = false;
}
//------------------------------------------------------------------------------
SGPath::PermissonChecker SGPath::getPermissonChecker() const
{
return _permission_checker;
}
//------------------------------------------------------------------------------
void SGPath::set_cached(bool cached) void SGPath::set_cached(bool cached)
{ {
_cacheEnabled = cached; _cacheEnabled = cached;
} }
// append another piece to the existing path // append another piece to the existing path
void SGPath::append( const string& p ) { void SGPath::append( const string& p ) {
if ( path.size() == 0 ) { if ( path.empty() ) {
path = p; path = p;
} else { } else {
if ( p[0] != sgDirPathSep ) { if ( p[0] != sgDirPathSep ) {
@@ -154,6 +213,7 @@ void SGPath::append( const string& p ) {
} }
fix(); fix();
_cached = false; _cached = false;
_rwCached = false;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -173,13 +233,14 @@ void SGPath::add( const string& p ) {
// concatenate a string to the end of the path without inserting a // concatenate a string to the end of the path without inserting a
// path separator // path separator
void SGPath::concat( const string& p ) { void SGPath::concat( const string& p ) {
if ( path.size() == 0 ) { if ( path.empty() ) {
path = p; path = p;
} else { } else {
path += p; path += p;
} }
fix(); fix();
_cached = false; _cached = false;
_rwCached = false;
} }
@@ -278,14 +339,21 @@ void SGPath::validate() const
if (_cached && _cacheEnabled) { if (_cached && _cacheEnabled) {
return; return;
} }
if (path.empty()) {
_exists = false;
return;
}
#ifdef _WIN32 #ifdef _WIN32
struct _stat buf ; struct _stat buf ;
bool remove_trailing = false; bool remove_trailing = false;
if ( path.length() > 1 && path[path.length()-1] == '/' ) string statPath(path);
remove_trailing=true; if ((path.length() > 1) && (path.back() == '/')) {
if (_stat (path.substr(0,remove_trailing?path.length()-1:path.length()).c_str(), &buf ) < 0) { statPath.pop_back();
}
if (_stat(statPath.c_str(), &buf ) < 0) {
_exists = false; _exists = false;
} else { } else {
_exists = true; _exists = true;
@@ -310,12 +378,46 @@ void SGPath::validate() const
_cached = true; _cached = true;
} }
void SGPath::checkAccess() const
{
if( _rwCached && _cacheEnabled )
return;
if( _permission_checker )
{
Permissions p = _permission_checker(*this);
_canRead = p.read;
_canWrite = p.write;
}
else
{
_canRead = true;
_canWrite = true;
}
_rwCached = true;
}
bool SGPath::exists() const bool SGPath::exists() const
{ {
validate(); validate();
return _exists; return _exists;
} }
//------------------------------------------------------------------------------
bool SGPath::canRead() const
{
checkAccess();
return _canRead;
}
//------------------------------------------------------------------------------
bool SGPath::canWrite() const
{
checkAccess();
return _canWrite;
}
bool SGPath::isDir() const bool SGPath::isDir() const
{ {
validate(); validate();
@@ -344,7 +446,7 @@ int SGPath::create_dir( mode_t mode ) {
bool absolute = !path.empty() && path[0] == sgDirPathSep; bool absolute = !path.empty() && path[0] == sgDirPathSep;
unsigned int i = 1; unsigned int i = 1;
SGPath dir = absolute ? string( 1, sgDirPathSep ) : ""; SGPath dir(absolute ? string( 1, sgDirPathSep ) : "", _permission_checker);
dir.concat( path_elements[0] ); dir.concat( path_elements[0] );
#ifdef _WIN32 #ifdef _WIN32
if ( dir.str().find(':') != string::npos && path_elements.size() >= 2 ) { if ( dir.str().find(':') != string::npos && path_elements.size() >= 2 ) {
@@ -360,16 +462,26 @@ int SGPath::create_dir( mode_t mode ) {
if ( r == 0 ) { if ( r == 0 ) {
return 0; // Directory already exists return 0; // Directory already exists
} }
if ( sgMkDir( dir.c_str(), mode) ) { for(;;)
SG_LOG( SG_IO, SG_ALERT, "Error creating directory: " + dir.str() ); {
if( !dir.canWrite() )
{
SG_LOG( SG_IO,
SG_WARN, "Error creating directory: (" << dir.str() << ")" <<
" reason: access denied" );
return -3;
}
else if( sgMkDir(dir.c_str(), mode) )
{
SG_LOG( SG_IO,
SG_ALERT, "Error creating directory: (" << dir.str() << ")" );
return -2; return -2;
} }
for(; i < path_elements.size(); i++) {
dir.append(path_elements[i]); if( i >= path_elements.size() )
if ( sgMkDir( dir.c_str(), mode) ) { return 0;
SG_LOG( SG_IO, SG_ALERT, "Error creating directory: " + dir.str() );
return -2; dir.append(path_elements[i++]);
}
} }
return 0; return 0;
@@ -378,7 +490,7 @@ int SGPath::create_dir( mode_t mode ) {
string_list sgPathBranchSplit( const string &dirpath ) { string_list sgPathBranchSplit( const string &dirpath ) {
string_list path_elements; string_list path_elements;
string element, path = dirpath; string element, path = dirpath;
while ( path.size() ) { while ( ! path.empty() ) {
size_t p = path.find( sgDirPathSep ); size_t p = path.find( sgDirPathSep );
if ( p != string::npos ) { if ( p != string::npos ) {
element = path.substr( 0, p ); element = path.substr( 0, p );
@@ -387,7 +499,7 @@ string_list sgPathBranchSplit( const string &dirpath ) {
element = path; element = path;
path = ""; path = "";
} }
if ( element.size() ) if ( ! element.empty() )
path_elements.push_back( element ); path_elements.push_back( element );
} }
return path_elements; return path_elements;
@@ -456,13 +568,27 @@ std::string SGPath::str_native() const
#endif #endif
} }
//------------------------------------------------------------------------------
bool SGPath::remove() bool SGPath::remove()
{ {
int err = ::unlink(c_str()); if( !canWrite() )
if (err) { {
SG_LOG(SG_IO, SG_WARN, "file remove failed: (" << str() << ") " << strerror(errno)); SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << str() << ")"
} " reason: access denied" );
return (err == 0); return false;
}
int err = ::unlink(c_str());
if( err )
{
SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << str() << ") "
" reason: " << strerror(errno) );
// TODO check if failed unlink can really change any of the cached values
}
_cached = false; // stat again if required
_rwCached = false;
return (err == 0);
} }
time_t SGPath::modTime() const time_t SGPath::modTime() const
@@ -481,17 +607,44 @@ bool SGPath::operator!=(const SGPath& other) const
return (path != other.path); return (path != other.path);
} }
//------------------------------------------------------------------------------
bool SGPath::rename(const SGPath& newName) bool SGPath::rename(const SGPath& newName)
{ {
if (::rename(c_str(), newName.c_str()) != 0) { if( !canRead() || !canWrite() || !newName.canWrite() )
SG_LOG(SG_IO, SG_WARN, "renamed failed: from " << str() << " to " << newName.str() {
<< " reason: " << strerror(errno)); SG_LOG( SG_IO, SG_WARN, "rename failed: from " << str() <<
return false; " to " << newName.str() <<
} " reason: access denied" );
return false;
path = newName.path; }
_cached = false;
return true; #ifdef SG_WINDOWS
if (newName.exists()) {
SGPath r(newName);
if (!r.remove()) {
return false;
}
}
#endif
if( ::rename(c_str(), newName.c_str()) != 0 )
{
SG_LOG( SG_IO, SG_WARN, "rename failed: from " << str() <<
" to " << newName.str() <<
" reason: " << strerror(errno) );
return false;
}
path = newName.path;
// Do not remove permission checker (could happen for example if just using
// a std::string as new name)
if( newName._permission_checker )
_permission_checker = newName._permission_checker;
_cached = false;
_rwCached = false;
return true;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -499,75 +652,68 @@ SGPath SGPath::fromEnv(const char* name, const SGPath& def)
{ {
const char* val = getenv(name); const char* val = getenv(name);
if( val && val[0] ) if( val && val[0] )
return SGPath(val); return SGPath(val, def._permission_checker);
return def; return def;
} }
#ifdef _WIN32 #ifdef _WIN32
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
SGPath SGPath::home() SGPath SGPath::home(const SGPath& def)
{ {
// TODO // TODO
return SGPath(); return def;
} }
#else #else
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
SGPath SGPath::home() SGPath SGPath::home(const SGPath& def)
{ {
return fromEnv("HOME"); return fromEnv("HOME", def);
} }
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
#include <ShlObj.h> // for CSIDL
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
SGPath SGPath::desktop() SGPath SGPath::desktop(const SGPath& def)
{ {
typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL); SGPath r = pathForCSIDL(CSIDL_DESKTOPDIRECTORY, def._permission_checker);
static GetSpecialFolderPath SHGetSpecialFolderPath = NULL; if (!r.isNull()) {
return r;
// lazy open+resolve of shell32
if (!SHGetSpecialFolderPath) {
HINSTANCE shellDll = ::LoadLibrary("shell32");
SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
}
if (!SHGetSpecialFolderPath){
return SGPath();
}
char path[MAX_PATH];
if (SHGetSpecialFolderPath(0, path, CSIDL_DESKTOPDIRECTORY, false)) {
return SGPath(path);
} }
SG_LOG(SG_GENERAL, SG_ALERT, "SGPath::desktop() failed, bad" ); SG_LOG(SG_GENERAL, SG_ALERT, "SGPath::desktop() failed, bad" );
return SGPath(); return def;
}
SGPath SGPath::documents(const SGPath& def)
{
SGPath r = pathForCSIDL(CSIDL_MYDOCUMENTS, def._permission_checker);
if (!r.isNull()) {
return r;
}
SG_LOG(SG_GENERAL, SG_ALERT, "SGPath::documents() failed, bad" );
return def;
} }
#elif __APPLE__ #elif __APPLE__
#include <CoreServices/CoreServices.h> #include <CoreServices/CoreServices.h>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
SGPath SGPath::desktop() SGPath SGPath::desktop(const SGPath& def)
{ {
FSRef ref; FSRef ref;
OSErr err = FSFindFolder(kUserDomain, kDesktopFolderType, false, &ref); OSErr err = FSFindFolder(kUserDomain, kDesktopFolderType, false, &ref);
if (err) { if (err)
return SGPath(); return def;
}
unsigned char path[1024]; unsigned char path[1024];
if (FSRefMakePath(&ref, path, 1024) != noErr) { if (FSRefMakePath(&ref, path, 1024) != noErr)
return SGPath(); return def;
}
return SGPath((const char*) path); return SGPath((const char*) path, def._permission_checker);
} }
#else #else
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
SGPath SGPath::desktop() SGPath SGPath::desktop(const SGPath& def)
{ {
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
@@ -594,12 +740,12 @@ SGPath SGPath::desktop()
const std::string HOME = "$HOME"; const std::string HOME = "$HOME";
if( starts_with(line, HOME) ) if( starts_with(line, HOME) )
return home() / simgear::strutils::unescape(line.substr(HOME.length())); return home(def) / simgear::strutils::unescape(line.substr(HOME.length()));
return SGPath(line); return SGPath(line, def._permission_checker);
} }
return home() / "Desktop"; return home(def) / "Desktop";
} }
#endif #endif
+39 -7
View File
@@ -52,8 +52,15 @@ class SGPath {
public: public:
struct Permissions
{
bool read : 1;
bool write : 1;
};
typedef Permissions (*PermissonChecker)(const SGPath&);
/** Default constructor */ /** Default constructor */
SGPath(); explicit SGPath(PermissonChecker validator = NULL);
/** Copy contructor */ /** Copy contructor */
SGPath(const SGPath& p); SGPath(const SGPath& p);
@@ -64,14 +71,16 @@ public:
* Construct a path based on the starting path provided. * Construct a path based on the starting path provided.
* @param p initial path * @param p initial path
*/ */
SGPath( const std::string& p ); SGPath( const std::string& p, PermissonChecker validator = NULL );
/** /**
* Construct a path based on the starting path provided and a relative subpath * Construct a path based on the starting path provided and a relative subpath
* @param p initial path * @param p initial path
* @param r relative subpath * @param r relative subpath
*/ */
SGPath( const SGPath& p, const std::string& r ); SGPath( const SGPath& p,
const std::string& r,
PermissonChecker validator = NULL );
/** Destructor */ /** Destructor */
~SGPath(); ~SGPath();
@@ -85,7 +94,10 @@ public:
bool operator==(const SGPath& other) const; bool operator==(const SGPath& other) const;
bool operator!=(const SGPath& other) const; bool operator!=(const SGPath& other) const;
void setPermissonChecker(PermissonChecker validator);
PermissonChecker getPermissonChecker() const;
/** /**
* Set if file information (exists, type, mod-time) is cached or * Set if file information (exists, type, mod-time) is cached or
* retrieved each time it is queried. Caching is enabled by default * retrieved each time it is queried. Caching is enabled by default
@@ -198,6 +210,17 @@ public:
*/ */
int create_dir(mode_t mode); int create_dir(mode_t mode);
/**
* Check if reading file is allowed. Readabilty does not imply the existance
* of the file.
*
* @note By default all files will be marked as readable. No check is made
* if the operating system allows the given file to be read. Derived
* classes may actually implement custom read/write rights.
*/
bool canRead() const;
bool canWrite() const;
bool isFile() const; bool isFile() const;
bool isDir() const; bool isDir() const;
@@ -246,23 +269,32 @@ public:
/** /**
* Get path to user's home directory * Get path to user's home directory
*/ */
static SGPath home(); static SGPath home(const SGPath& def = SGPath());
/** /**
* Get path to the user's desktop directory * Get path to the user's desktop directory
*/ */
static SGPath desktop(); static SGPath desktop(const SGPath& def = SGPath());
/**
* Get path to the user's documents directory
*/
static SGPath documents(const SGPath& def = SGPath());
private: private:
void fix(); void fix();
void validate() const; void validate() const;
void checkAccess() const;
std::string path; std::string path;
PermissonChecker _permission_checker;
mutable bool _cached : 1; mutable bool _cached : 1;
mutable bool _rwCached : 1;
bool _cacheEnabled : 1; ///< cacheing can be disbled if required bool _cacheEnabled : 1; ///< cacheing can be disbled if required
mutable bool _canRead : 1;
mutable bool _canWrite : 1;
mutable bool _exists : 1; mutable bool _exists : 1;
mutable bool _isDir : 1; mutable bool _isDir : 1;
mutable bool _isFile : 1; mutable bool _isFile : 1;
+110 -23
View File
@@ -26,6 +26,8 @@
#include "strutils.hxx" #include "strutils.hxx"
#include <simgear/debug/logstream.hxx>
using std::string; using std::string;
using std::vector; using std::vector;
using std::stringstream; using std::stringstream;
@@ -33,6 +35,39 @@ using std::stringstream;
namespace simgear { namespace simgear {
namespace strutils { namespace strutils {
/*
* utf8ToLatin1() convert utf8 to latin, useful for accent character (i.e éâàîè...)
*/
template <typename Iterator> size_t get_length (Iterator p) {
unsigned char c = static_cast<unsigned char> (*p);
if (c < 0x80) return 1;
else if (!(c & 0x20)) return 2;
else if (!(c & 0x10)) return 3;
else if (!(c & 0x08)) return 4;
else if (!(c & 0x04)) return 5;
else return 6;
}
typedef unsigned int value_type;
template <typename Iterator> value_type get_value (Iterator p) {
size_t len = get_length (p);
if (len == 1) return *p;
value_type res = static_cast<unsigned char> ( *p & (0xff >> (len + 1))) << ((len - 1) * 6 );
for (--len; len; --len)
res |= (static_cast<unsigned char> (*(++p)) - 0x80) << ((len - 1) * 6);
return res;
}
string utf8ToLatin1( string& s_utf8 ) {
string s_latin1;
for (string::iterator p = s_utf8.begin(); p != s_utf8.end(); ++p) {
value_type value = get_value<string::iterator&>(p);
if (value > 0xff) SG_LOG(SG_IO, SG_WARN, "utf8ToLatin1: wrong char value: " << value);
s_latin1 += static_cast<char>(value);
}
return s_latin1;
}
/** /**
* *
*/ */
@@ -303,26 +338,51 @@ namespace simgear {
return rslt; return rslt;
} }
string lowercase(const string &s) {
string rslt(s);
for(string::iterator p = rslt.begin(); p != rslt.end(); p++){
*p = tolower(*p);
}
return rslt;
}
void lowercase(string &s) {
for(string::iterator p = s.begin(); p != s.end(); p++){
*p = tolower(*p);
}
}
#if defined(SG_WINDOWS)
#include <windows.h>
static WCharVec convertMultiByteToWString(DWORD encoding, const std::string& a)
{
WCharVec result;
DWORD flags = 0;
int requiredWideChars = MultiByteToWideChar(encoding, flags,
a.c_str(), a.size(),
NULL, 0);
result.resize(requiredWideChars);
MultiByteToWideChar(encoding, flags, a.c_str(), a.size(),
result.data(), result.size());
return result;
}
WCharVec convertUtf8ToWString(const std::string& a)
{
return convertMultiByteToWString(CP_UTF8, a);
}
#ifdef SG_WINDOWS
#include <windows.h>
#endif #endif
std::string convertWindowsLocal8BitToUtf8(const std::string& a) std::string convertWindowsLocal8BitToUtf8(const std::string& a)
{ {
#ifdef SG_WINDOWS #ifdef SG_WINDOWS
DWORD flags = 0; DWORD flags = 0;
std::vector<wchar_t> wideString; WCharVec wideString = convertMultiByteToWString(CP_ACP, a);
// call to query transform size // convert down to UTF-8
int requiredWideChars = MultiByteToWideChar(CP_ACP, flags, a.c_str(), a.size(),
NULL, 0);
// allocate storage and call for real
wideString.resize(requiredWideChars);
MultiByteToWideChar(CP_ACP, flags, a.c_str(), a.size(),
wideString.data(), wideString.size());
// now convert back down to UTF-8
std::vector<char> result; std::vector<char> result;
int requiredUTF8Chars = WideCharToMultiByte(CP_UTF8, flags, int requiredUTF8Chars = WideCharToMultiByte(CP_UTF8, flags,
wideString.data(), wideString.size(), wideString.data(), wideString.size(),
@@ -337,12 +397,31 @@ std::string convertWindowsLocal8BitToUtf8(const std::string& a)
#endif #endif
} }
static const std::string base64_chars = static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyz"
"0123456789+/"; "0123456789+/";
static const unsigned char base64_decode_map[128] =
{
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 62, 127, 127, 127, 63, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, 127, 127,
127, 64, 127, 127, 127, 0, 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, 127, 127, 127, 127, 127, 127, 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, 127, 127, 127, 127, 127
};
static inline bool is_base64(unsigned char c) { static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/')); return (isalnum(c) || (c == '+') || (c == '/'));
} }
@@ -351,14 +430,13 @@ static bool is_whitespace(unsigned char c) {
return ((c == ' ') || (c == '\r') || (c == '\n')); return ((c == ' ') || (c == '\r') || (c == '\n'));
} }
std::string decodeBase64(const std::string& encoded_string) void decodeBase64(const std::string& encoded_string, std::vector<unsigned char>& ret)
{ {
int in_len = encoded_string.size(); int in_len = encoded_string.size();
int i = 0; int i = 0;
int j = 0; int j = 0;
int in_ = 0; int in_ = 0;
unsigned char char_array_4[4], char_array_3[3]; unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && ( encoded_string[in_] != '=')) { while (in_len-- && ( encoded_string[in_] != '=')) {
if (is_whitespace( encoded_string[in_])) { if (is_whitespace( encoded_string[in_])) {
@@ -373,14 +451,14 @@ std::string decodeBase64(const std::string& encoded_string)
char_array_4[i++] = encoded_string[in_]; in_++; char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) { if (i ==4) {
for (i = 0; i <4; i++) for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]); char_array_4[i] = base64_decode_map[char_array_4[i]];
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++) for (i = 0; (i < 3); i++)
ret += char_array_3[i]; ret.push_back(char_array_3[i]);
i = 0; i = 0;
} }
} }
@@ -390,16 +468,14 @@ std::string decodeBase64(const std::string& encoded_string)
char_array_4[j] = 0; char_array_4[j] = 0;
for (j = 0; j <4; j++) for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]); char_array_4[j] = base64_decode_map[char_array_4[j]];
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; for (j = 0; (j < i - 1); j++) ret.push_back(char_array_3[j]);
} }
return ret;
} }
const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
@@ -484,6 +560,17 @@ std::string unescape(const char* s)
return r; return r;
} }
string sanitizePrintfFormat(const string& input)
{
string::size_type i = input.find("%n");
if (i != string::npos) {
SG_LOG(SG_IO, SG_WARN, "sanitizePrintfFormat: bad format string:" << input);
return string();
}
return input;
}
} // end namespace strutils } // end namespace strutils
} // end namespace simgear } // end namespace simgear
+30 -2
View File
@@ -38,6 +38,11 @@ typedef std::vector < std::string > string_list;
namespace simgear { namespace simgear {
namespace strutils { namespace strutils {
/**
* utf8ToLatin1() convert utf8 to latin, useful for accent character (i.e éâàîè...)
*/
std::string utf8ToLatin1( std::string & s_utf8 );
// /** // /**
// * atof() wrapper for "string" type // * atof() wrapper for "string" type
// */ // */
@@ -137,7 +142,7 @@ namespace simgear {
/** /**
* Like strcmp(), but for dotted versions strings NN.NN.NN * Like strcmp(), but for dotted versions strings NN.NN.NN
* any number of terms are support. * any number of terms are supported.
* @return 0 if versions match, -ve number if v1 is lower, +ve if v1 * @return 0 if versions match, -ve number if v1 is lower, +ve if v1
* is greater * is greater
*/ */
@@ -149,18 +154,34 @@ namespace simgear {
*/ */
std::string uppercase(const std::string &s); std::string uppercase(const std::string &s);
/**
* Convert a string to lower case.
* @return lower case string
*/
std::string lowercase(const std::string &s);
/**
* Convert a string to lower case in place
*/
void lowercase(std::string &s);
/** /**
* convert a string in the local Windows 8-bit encoding to UTF-8 * convert a string in the local Windows 8-bit encoding to UTF-8
* (no-op on other platforms) * (no-op on other platforms)
*/ */
std::string convertWindowsLocal8BitToUtf8(const std::string& a); std::string convertWindowsLocal8BitToUtf8(const std::string& a);
#if defined(SG_WINDOWS)
typedef std::vector<wchar_t> WCharVec;
WCharVec convertUtf8ToWString(const std::string& a);
#endif
/** /**
* convert base-64 encoded data to raw bytes (possibly with embedded * convert base-64 encoded data to raw bytes (possibly with embedded
* NULs). Throws an exception if input data is not base64, or is * NULs). Throws an exception if input data is not base64, or is
* malformed * malformed
*/ */
std::string decodeBase64(const std::string& a); void decodeBase64(const std::string& a, std::vector<unsigned char>& output);
/** /**
* convert bytes to hexadecimal equivalent * convert bytes to hexadecimal equivalent
@@ -180,6 +201,13 @@ namespace simgear {
inline std::string unescape(const std::string& str) inline std::string unescape(const std::string& str)
{ return unescape(str.c_str()); } { return unescape(str.c_str()); }
/**
* Check a printf-style format string for dangerous (buffer-overflowing,
* memory re-writing) format tokens. If a problematic token is
* found, logs an error (SG_WARN) and returns an empty format string.
*/
std::string sanitizePrintfFormat(const std::string& input);
} // end namespace strutils } // end namespace strutils
} // end namespace simgear } // end namespace simgear

Some files were not shown because too many files have changed in this diff Show More