4 // Copyright (C) 2005 Novell, Inc.
7 // Vijay K. Nanjundaswamy (knvijay@novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining a
12 // copy of this software and associated documentation files (the "Software"),
13 // to deal in the Software without restriction, including without limitation
14 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 // and/or sell copies of the Software, and to permit persons to whom the
16 // Software is furnished to do so, subject to the following conditions:
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 // DEALINGS IN THE SOFTWARE.
31 using System
.Collections
;
32 using System
.Diagnostics
;
33 using System
.Threading
;
34 using System
.Reflection
;
37 using System
.Runtime
.Remoting
;
38 using System
.Runtime
.Remoting
.Channels
;
39 using System
.Runtime
.Remoting
.Channels
.Tcp
;
43 using BT
= Beagle
.Tile
;
46 namespace Beagle
.WebService
{
52 public string searchString
;
53 public string searchSource
;
54 public bool isLocalReq
;
55 public bool globalSearch
;
58 public class WebBackEnd
: MarshalByRefObject
{
60 static WebBackEnd instance
= null;
61 static Logger log
= Logger
.Get ("WebBackEnd");
63 private Hashtable result
;
64 private Hashtable sessionResp
;
67 result
= Hashtable
.Synchronized(new Hashtable());
68 sessionResp
= Hashtable
.Synchronized(new Hashtable());
76 public bool allowGlobalAccess
{
77 get { return WebServiceBackEnd.web_global; }
80 public string HostName
{
81 get { return WebServiceBackEnd.hostname; }
84 static TcpChannel tch1
= null;
85 public static void init()
87 if (instance
== null) {
88 instance
= new WebBackEnd();
92 tch1
= new TcpChannel(8347);
94 //Register TCP Channel Listener
95 ChannelServices
.RegisterChannel(tch1
);
97 WellKnownServiceTypeEntry WKSTE
=
98 new WellKnownServiceTypeEntry(typeof(WebBackEnd
),
99 "WebBackEnd.rem", WellKnownObjectMode
.Singleton
);
100 RemotingConfiguration
.ApplicationName
="beagled";
101 RemotingConfiguration
.RegisterWellKnownServiceType(WKSTE
);
106 public static void cleanup()
109 tch1
.StopListening(null);
110 ChannelServices
.UnregisterChannel(tch1
);
117 void OnHitsAdded (QueryResult qres
, ICollection hits
)
119 if (result
.Contains(qres
)) {
121 Resp resp
= ((Resp
) result
[qres
]);
122 BT
.SimpleRootTile root
= resp
.resultPair
.rootTile
;
123 ArrayList hitsCopy
= resp
.resultPair
.hitsCopy
;
126 if (resp
.isLocalReq
) {
128 lock (hitsCopy
.SyncRoot
)
129 hitsCopy
.AddRange(hits
);
132 foreach (Hit h
in hits
)
133 if (h
.UriAsString
.StartsWith(NetworkedBeagle
.BeagleNetPrefix
) ||
134 WebServiceBackEnd
.AccessFilter
.FilterHit(h
)) {
136 lock (hitsCopy
.SyncRoot
)
144 void removeUris(ArrayList res
, ICollection uris
)
146 foreach(Uri u
in uris
)
147 foreach(Hit h
in res
)
148 if (h
.Uri
.Equals (u
) && h
.Uri
.Fragment
== u
.Fragment
) {
149 lock (res
.SyncRoot
) {
156 void OnHitsSubtracted (QueryResult qres
, ICollection uris
)
158 if (result
.Contains(qres
)) {
159 BT
.SimpleRootTile root
= ((Resp
) result
[qres
]).resultPair
.rootTile
;
161 root
.Subtract (uris
);
162 removeUris(((Resp
) result
[qres
]).resultPair
.hitsCopy
, uris
);
167 void OnFinished (QueryResult qres
)
169 if (result
.Contains(qres
))
170 log
.Info("WebBackEnd:OnFinished() - Got {0} results from beagled QueryDriver", ((Resp
) result
[qres
]).resultPair
.rootTile
.HitCollection
.NumResults
);
172 DetachQueryResult(qres
);
175 void OnCancelled (QueryResult qres
)
177 DetachQueryResult(qres
);
180 private void AttachQueryResult (QueryResult qres
, Resp resp
)
184 qres
.HitsAddedEvent
+= OnHitsAdded
;
185 qres
.HitsSubtractedEvent
+= OnHitsSubtracted
;
186 qres
.FinishedEvent
+= OnFinished
;
187 qres
.CancelledEvent
+= OnCancelled
;
189 result
.Add(qres
, resp
);
193 private void DetachQueryResult (QueryResult qres
)
197 if (result
.Contains(qres
))
199 Resp resp
= ((Resp
) result
[qres
]);
200 ArrayList hitsCopy
= resp
.resultPair
.hitsCopy
;
201 if (hitsCopy
!= null)
204 resp
.bufferContext
.maxDisplayed
= 0;
209 qres
.HitsAddedEvent
-= OnHitsAdded
;
210 qres
.HitsSubtractedEvent
-= OnHitsSubtracted
;
211 qres
.FinishedEvent
-= OnFinished
;
212 qres
.CancelledEvent
-= OnCancelled
;
218 const string NO_RESULTS
= "No results.";
220 private string getResultsLabel(BT
.SimpleRootTile root
)
223 if (root
.HitCollection
.NumResults
== 0)
225 else if (root
.HitCollection
.FirstDisplayed
== 0)
226 label
= String
.Format ("<b>{0} results of {1}</b> are shown.",
227 root
.HitCollection
.LastDisplayed
+ 1,
228 root
.HitCollection
.NumResults
);
230 label
= String
.Format ("Results <b>{0} through {1} of {2}</b> are shown.",
231 root
.HitCollection
.FirstDisplayed
+ 1,
232 root
.HitCollection
.LastDisplayed
+ 1,
233 root
.HitCollection
.NumResults
);
237 public bool canForward(string sessId
)
239 Resp resp
= (Resp
) sessionResp
[sessId
];
243 BT
.SimpleRootTile root
= resp
.resultPair
.rootTile
;
244 return (root
!= null)? root
.HitCollection
.CanPageForward
:false;
247 public string doForward(string sessId
)
249 Resp resp
= (Resp
) sessionResp
[sessId
];
251 if (!canForward(sessId
) || (resp
== null))
254 BT
.SimpleRootTile root
= resp
.resultPair
.rootTile
;
257 root
.HitCollection
.PageForward ();
259 bufferRenderContext bctx
= resp
.bufferContext
;
262 return (getResultsLabel(root
) + (resp
.isLocalReq
? bctx
.buffer
:bctx
.bufferForExternalQuery
));
269 public bool canBack(string sessId
)
271 Resp resp
= (Resp
) sessionResp
[sessId
];
275 BT
.SimpleRootTile root
= resp
.resultPair
.rootTile
;
276 return (root
!= null) ? root
.HitCollection
.CanPageBack
:false;
279 public string doBack(string sessId
)
281 Resp resp
= (Resp
) sessionResp
[sessId
];
282 if (!canBack(sessId
) || (resp
== null))
285 BT
.SimpleRootTile root
= resp
.resultPair
.rootTile
;
289 root
.HitCollection
.PageBack();
291 bufferRenderContext bctx
= resp
.bufferContext
;
294 return (getResultsLabel(root
) + (resp
.isLocalReq
? bctx
.buffer
:bctx
.bufferForExternalQuery
));
301 public bool NetworkBeagleActive
303 get {return NetworkedBeagle.NetBeagleListActive;}
306 public string doQuery(webArgs wargs
)
308 if (wargs
.sessId
== null || wargs
.searchString
== null || wargs
.searchString
== "")
311 log
.Debug("WebBackEnd: Got Search String: " + wargs
.searchString
);
313 Query query
= new Query();
314 query
.AddText (wargs
.searchString
);
315 if (wargs
.searchSource
!= null && wargs
.searchSource
!= "")
317 query
.AddSource(wargs
.searchSource
);
318 query
.AddDomain(QueryDomain
.System
);
321 query
.AddDomain (wargs
.globalSearch
? QueryDomain
.Global
:QueryDomain
.System
);
323 QueryResult qres
= new QueryResult ();
325 //Note: QueryDriver.DoQuery() local invocation is used.
326 //The root tile is used only for adding hits and generating html.
327 BT
.SimpleRootTile root
= new BT
.SimpleRootTile ();
329 //root.SetSource (searchSource); Do not SetSource on root!
331 ResultPair rp
= new ResultPair(root
);
332 bufferRenderContext bctx
= new bufferRenderContext(rp
);
333 Resp resp
= new Resp(rp
, bctx
, wargs
.isLocalReq
);
335 AttachQueryResult (qres
, resp
);
337 //Add sessionId-Resp mapping
338 if (sessionResp
.Contains(wargs
.sessId
))
339 sessionResp
[wargs
.sessId
] = resp
;
341 sessionResp
.Add(wargs
.sessId
, resp
);
343 log
.Info("WebBackEnd: Starting Query for string \"{0}\"", wargs
.searchString
);
345 QueryDriver
.DoQueryLocal (query
, qres
);
347 //Wait only till we have enough results to display
348 while ((result
.Contains(qres
)) &&
349 (root
.HitCollection
.NumResults
< 10))
352 if (root
.HitCollection
.IsEmpty
)
357 return (getResultsLabel(root
) + (wargs
.isLocalReq
? bctx
.buffer
:bctx
.bufferForExternalQuery
));
361 public void dispatchAction (string sessId
, string actionString
)
363 string tile_id
= null, action
= null;
364 bool actionDone
= false;
366 //if (actionString.StartsWith ("dynaction:")) {
368 bufferRenderContext b
= ((Resp
)sessionResp
[sessId
]).bufferContext
;
370 actionDone
= b
.DoAction(actionString
);
376 if (actionString
.StartsWith ("action:")) {
378 int pos1
= "action:".Length
;
379 int pos2
= actionString
.IndexOf ("!");
384 tile_id
= actionString
.Substring (pos1
, pos2
- pos1
);
385 action
= actionString
.Substring (pos2
+ 1);
387 log
.Debug("WebBackEnd tile_id: {0}, action: {1}", tile_id
, action
);
389 BT
.Tile t
= ((Resp
)sessionResp
[sessId
]).GetTile (tile_id
);
394 MethodInfo info
= t
.GetType().GetMethod (action
,
395 BindingFlags
.Public
| BindingFlags
.NonPublic
|
396 BindingFlags
.Instance
, null,
397 CallingConventions
.Any
, new Type
[] {}, null);
400 log
.Warn ("WebBackEnd:dispatchAction couldn't find method called {0}", action
);
404 object[] attrs
= info
.GetCustomAttributes (false);
405 foreach (object attr
in attrs
) {
406 if (attr
is BT
.TileActionAttribute
) {
407 info
.Invoke (t
, null);
411 log
.Warn ("WebBackEnd:dispatchAction {0} does not have the TileAction attribute", t
);
414 string command
= null;
415 string commandArgs
= null;
417 if (actionString
.StartsWith ("http://") || actionString
.StartsWith ("file://")) {
418 command
= "gnome-open";
419 commandArgs
= "'" + actionString
+ "'";
421 else if (actionString
.StartsWith ("mailto:")) {
422 command
= "evolution";
423 commandArgs
= actionString
;
426 if (command
!= null) {
427 Process p
= new Process ();
428 p
.StartInfo
.UseShellExecute
= false;
429 p
.StartInfo
.FileName
= command
;
430 if (commandArgs
!= null)
432 p
.StartInfo
.Arguments
= commandArgs
;
441 //////////////////////////////////////////////////////////////////////////
443 private class ResultPair
{
444 private BT
.SimpleRootTile _rootTile
;
445 private ArrayList _hitsCopy
;
447 public ResultPair(BT
.SimpleRootTile rootTile
) {
448 this._rootTile
= rootTile
;
449 _hitsCopy
= ArrayList
.Synchronized(new ArrayList());
452 public BT
.SimpleRootTile rootTile
{
453 get { return _rootTile; }
456 public ArrayList hitsCopy
{
457 get { return _hitsCopy; }
463 private ResultPair _rp
;
464 private bufferRenderContext bufCtx
= null;
465 private bool _localRequest
;
467 private Hashtable tileTab
= null;
469 public Resp(ResultPair rp
, bufferRenderContext bCtx
, bool isLocalReq
)
473 this._localRequest
= isLocalReq
;
475 this.tileTab
= bCtx
.table
;
478 public ResultPair resultPair
{
481 public bufferRenderContext bufferContext
{
482 get { return bufCtx; }
484 public bool isLocalReq
{
485 get { return _localRequest; }
488 public BT
.Tile
GetTile (string key
)
491 return resultPair
.rootTile
;
493 return (Beagle
.Tile
.Tile
) tileTab
[key
];
497 //////////////////////////////////////////////////////////////////////////
498 private class bufferRenderContext
: BT
.TileRenderContext
{
500 private ResultPair _rp
;
501 private Hashtable tileTable
= null;
502 private Hashtable actionTable
= null;
505 private System
.Text
.StringBuilder sb
;
506 private bool renderStylesDone
= false;
508 public bufferRenderContext (ResultPair rp
)
511 this.tileTable
= Hashtable
.Synchronized(new Hashtable());
512 this.actionTable
= new Hashtable ();
516 public string buffer
{
517 get { return sb.ToString(); }
520 public Hashtable table
{
521 get { return tileTable; }
524 public string bufferForExternalQuery
{
527 //Substitute "action:_tile_id!Open" with "http://host:port/beagle?xxxx"
529 string[] list
= sb
.ToString().Split('\"');
530 for (int i
= 0; i
< list
.Length
; i
++) {
533 if (s
.StartsWith("action") && s
.EndsWith("!Open")) {
535 string[] s1
= s
.Split(':');
537 string[] s2
= s1
[1].Split('!');
539 BT
.Tile t
= (BT
.Tile
) table
[s2
[0]];
540 list
[i
] = WebServiceBackEnd
.AccessFilter
.TranslateHit(t
.Hit
);
541 t
.Uri
= new Uri(list
[i
]);
546 return String
.Join ("\"", list
);
553 sb
= new StringBuilder(4096);
554 renderStylesDone
= false;
557 tileTable
[_rp
.rootTile
.UniqueKey
] = _rp
.rootTile
;
560 /////////////////////////////////////////////////
561 public void ClearActions ()
567 private string AddAction (BT
.TileActionHandler handler
)
570 return "dynaction:NULL";
571 string key
= "dynaction:" + actionId
.ToString ();
573 actionTable
[key
] = handler
;
577 public bool DoAction (string key
)
579 BT
.TileActionHandler handler
= (BT
.TileActionHandler
) actionTable
[key
];
580 if (handler
!= null) {
586 /////////////////////////////////////////////////
588 override public void Write (string markup
)
593 override public void Link (string label
,
594 BT
.TileActionHandler handler
)
596 string key
= AddAction (handler
);
597 Write ("<a href=\"{0}\">{1}</a>", key
, label
);
600 override public void Tile (BT
.Tile tile
)
602 tileTable
[tile
.UniqueKey
] = tile
;
604 if (!renderStylesDone
) {
605 //KNV: Using static_stylesheet for now. Replace with TileCanvas logic later:
606 Write(static_stylesheet
);
608 Write ("<style type=\"text/css\" media=\"screen\">");
609 TileCanvas.RenderStyles (this);
612 renderStylesDone
= true;
617 if (tile
is BT
.TileHitCollection
)
618 PrefetchSnippetsForNetworkHits((BT
.TileHitCollection
)tile
);
623 /////////////////////////////////////////////////
624 // Code to scan forward through result set & prefetch/cache Snippets for Network Hits
626 public int maxDisplayed
= 0;
627 const int MAX_HIT_IDS_PER_REQ
= 20; //Max no. of hits snippets to seek at a time
628 const int MAX_HITS_AHEAD
= 40; //No. of hits ahead of lastDisplayed to scan
630 private bool tenHits
= false; //Flag to do Prefetch check only every 10 hits
632 private void PrefetchSnippetsForNetworkHits(BT
.TileHitCollection thc
)
634 int lastDisplayed
= 0;
636 if (maxDisplayed
!= 0)
637 lastDisplayed
= thc
.LastDisplayed
+ 1;
639 //We have cached snippets for network hits upto maxDisplayed
640 if (lastDisplayed
< maxDisplayed
)
643 maxDisplayed
= thc
.LastDisplayed
+ 1;
645 //Do Prefetch check once every ten hits
650 if (lastDisplayed
< thc
.NumResults
) {
653 ArrayList networkHits
= new ArrayList();
655 if ((thc
.NumResults
- lastDisplayed
) > MAX_HITS_AHEAD
)
656 limit
= lastDisplayed
+ MAX_HITS_AHEAD
;
658 limit
= thc
.NumResults
;
660 ArrayList hits
= _rp
.hitsCopy
;
661 lock (hits
.SyncRoot
) {
663 if (limit
> hits
.Count
)
666 log
.Debug("PrefetchSnippets: Scanning result set for Network Hits from {0} to {1}", lastDisplayed
, limit
);
668 //Get all NetworkHits with snippets field not initialized:
669 for (int si
= lastDisplayed
; si
< limit
; si
++)
671 if ((hits
[si
] is NetworkHit
) && (((NetworkHit
)hits
[si
]).snippet
== null))
672 networkHits
.Add((NetworkHit
)hits
[si
]);
676 log
.Debug("PrefetchSnippets: Found {0} NetworkHits without snippets", networkHits
.Count
);
678 while (networkHits
.Count
> 0) {
680 ArrayList nwHitsPerNode
= new ArrayList();
681 string hostnamePort
= GetHostnamePort((NetworkHit
)networkHits
[0]);
683 //Gather NetworkHits from a specific target Networked Beagle
684 foreach (NetworkHit nh
in networkHits
)
686 string hnp
= GetHostnamePort(nh
);
690 if (hnp
.Equals(hostnamePort
)) {
692 if (nwHitsPerNode
.Count
< MAX_HIT_IDS_PER_REQ
)
693 nwHitsPerNode
.Add(nh
);
699 //Remove NetworkHits for this Networked Beagle
700 int i
= networkHits
.Count
;
703 string hnp
= GetHostnamePort((NetworkHit
)networkHits
[i
]);
704 if ((hnp
== null) || hnp
.Equals(hostnamePort
))
705 networkHits
.RemoveAt(i
);
708 if (nwHitsPerNode
.Count
> 0)
710 string[] f3
= hostnamePort
.Split(':');
713 log
.Warn("PrefetchSnippets: Invalid format netBeagle URI in NetworkHit");
716 BeagleWebService wsp
= new BeagleWebService(f3
[0], f3
[1]);
718 string searchToken
= GetSearchToken((NetworkHit
)nwHitsPerNode
[0]);
720 if (searchToken
.Equals("beagle")) //Check if it is Older version of Beagle networking
723 if (searchToken
!= null) {
725 int[] hitHashCodes
= new int [nwHitsPerNode
.Count
];
726 for (int j
= 0; j
< hitHashCodes
.Length
; j
++)
727 hitHashCodes
[j
] = ((NetContext
) ((NetworkHit
)nwHitsPerNode
[j
]).context
).hashCode
;
729 log
.Debug("PrefetchSnippets: Invoking GetSnippets on {0} for {1} hits", wsp
.Hostname
, nwHitsPerNode
.Count
);
731 GetSnippetsRequest sreq
= new GetSnippetsRequest();
732 sreq
.searchToken
= searchToken
;
733 sreq
.hitHashCodes
= hitHashCodes
;
735 ReqContext2 rc
= new ReqContext2(wsp
, nwHitsPerNode
, thc
);
736 wsp
.BeginGetSnippets(sreq
, PrefetchSnippetsResponseHandler
, rc
);
739 //Signal change in TileHitCollection due to addition of snippets:
740 //_rp.rootTile.HitCollection.ClearSources(null);
746 private static void PrefetchSnippetsResponseHandler(IAsyncResult ar
)
748 ReqContext2 rc
= (ReqContext2
)ar
.AsyncState
;
750 ArrayList nwHits
= rc
.GetNwHits
;
751 BeagleWebService wsp
= rc
.GetProxy
;
755 Beagle
.Daemon
.HitSnippet
[] hslist
= wsp
.EndGetSnippets(ar
);
758 if (hslist
.Length
> 0)
760 log
.Debug("PrefetchSnippetsResponseHandler: Got {0} snippet responses from {1}", hslist
.Length
, wsp
.Hostname
);
762 foreach (Beagle
.Daemon
.HitSnippet hs
in hslist
) {
768 hitHashCode
= hs
.hashCode
;
769 snippet
= hs
.snippet
;
771 catch (Exception ex2
)
773 log
.Warn ("Exception in WebBackEnd: PrefetchSnippetsResponseHandler(), while getting snippet from {1}\n Reason: {2} ", wsp
.Hostname
+ ":" + wsp
.Port
, ex2
.Message
);
777 if (snippet
.StartsWith(WebServiceBackEnd
.InvalidHitSnippetError
))
780 for (i
= 0; i
< nwHits
.Count
; i
++)
781 if ( ((NetContext
) ((NetworkHit
)nwHits
[i
]).context
).hashCode
== hitHashCode
) {
783 ((NetworkHit
)nwHits
[i
]).snippet
= snippet
;
784 //log.Debug("\nPrefetchSnippetsResponseHandler: URI" + j++ + "=" + ((NetworkHit)nwHits[i]).UriAsString + "\n Snippet=" + snippet);
788 if (i
< nwHits
.Count
)
793 catch (Exception ex
) {
794 log
.Error ("Exception in WebBackEnd: PrefetchSnippetsResponseHandler() - {0} - for {1} ", ex
.Message
, wsp
.Hostname
+ ":" + wsp
.Port
);
797 if (nwHits
.Count
> 0) {
798 //Possible Error in getting snippets for these hitIds
799 log
.Warn("WebBackEnd/PrefetchSnippetsResponseHandler(): Didn't get Snippets for some network Hits");
801 foreach (NetworkHit nh
in nwHits
)
805 //Signal change in TileHitCollection due to addition of snippets:
806 rc
.GetHitCollection
.ClearSources(null);
809 private class ReqContext2
{
811 BT
.TileHitCollection _thc
;
812 BeagleWebService _wsp
;
815 public ReqContext2(BeagleWebService wsp
, ArrayList nwHits
, BT
.TileHitCollection thc
)
819 this._nwHits
= nwHits
;
822 public BT
.TileHitCollection GetHitCollection
{
826 public BeagleWebService GetProxy
{
830 public ArrayList GetNwHits
{
831 get { return _nwHits; }
836 private string GetSearchToken(NetworkHit nh
)
841 string netUri
= nh
.UriAsString
;
843 //netbeagle://164.99.153.134:8888/searchToken?http:///....
844 string[] f1
, f2
= netUri
.Split('?');
846 f1
= f2
[0].Split ('/');
848 return (f1
[f1
.Length
- 1]);
853 private string GetHostnamePort(NetworkHit nh
)
858 string netUri
= nh
.UriAsString
;
860 //netbeagle://164.99.153.134:8888/searchToken?http:///....
861 string[] f1
, f2
= netUri
.Split('?');
863 f1
= f2
[0].Split ('/');
870 //////////////////////////////////////////////////////////////////////////
872 //string static_stylesheet = "<style type=\"text/css\" media=\"screen\"> body, html { background: white; margin: 0; padding: 0; font-family: Sans, Segoe, Trebuchet MS, Lucida, Sans-Serif; text-align: left; line-height: 1.5em; } a, a:visited { text-decoration: none; color: #2b5a8a; } a:hover { text-decoration: underline; } img { border: 0px; } table { width: 100%; border-collapse: collapse; font-size: 10px; } tr { border-bottom: 1px dotted #999999; } tr:hover { background: #f5f5f5; } tr:hover .icon { background-color: #ddddd0; } td { padding: 6px; } td.icon { background-color: #eeeee0; min-height: 80px; width: 1%; min-width: 80px; text-align: center; vertical-align: top; padding: 12px; } .icon img { max-width: 60px; padding: 4px; } .icon img[src$='.jpg'], img[src$='.jpeg'], img[src*='.thumbnails'] {// max-width: 48px; border: 1px dotted #bbb; // padding: 4px; background: #f9f9f9; } td.content { padding-left: 12px; vertical-align: top; } #hilight { background-color: #ffee66; color: #000000; padding-left: 2px; padding-right: 2px; margin-left: -2px; margin-right: -2px; } .name {font-size: 1.3em; font-weight: bold; color: black; } .date { font-size: 1em; color: black; margin-bottom: 0.6em; margin-top: 0.2em; margin-left: 16px; } .snippet {font-size: 1em; color: gray; margin-left: 16px; } .url {font-size: 1em; color: #008200; margin-left: 16px; } ul {margin-left: 16px; padding: 0px; clear: both; } .actions { font-size: 1em; } .actions li { float: left; display: block; vertical-align: middle; padding: 0; padding-left: 20px; padding-right: 12px; background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/navigation/stock_right.png) no-repeat; min-height: 16px; -moz-opacity: 0.5; } tr:hover .actions li { -moz-opacity: 1.0; } #phone { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/generic/stock_landline-phone.png) no-repeat; } #email { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/net/stock_mail.png) no-repeat; } #email-forward { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/net/stock_mail-forward.png) no-repeat; } #email-reply { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/net/stock_mail-reply.png) no-repeat; } #message { background: url(file:///opt/gnome/share/icons/hicolor/16x16/apps/im-yahoo.png) no-repeat; } #reveal { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/io/stock_open.png) no-repeat; } td.footer { text-align: right; border-bottom: solid 1px white; } </style>";
873 string static_stylesheet
= "<style type=\"text/css\" media=\"screen\"> body, html { background: white; margin: 0; padding: 0; font-family: Arial KOI-8, Segoe, Trebuchet MS, Lucida, Sans-Serif; text-align: left; line-height: 1.5em; } a, a:visited { text-decoration: none; color: #2b5a8a; } a:hover { text-decoration: underline; } img { border: 0px; } table { width: 100%; border-collapse: collapse; font-size: 11px; } tr { border-bottom: 1px dotted #999999; } tr:hover { background: #f5f5f5; } tr:hover .icon { background-color: #ddddd0; } td { padding: 6px; } td.icon { background-color: #eeeee0; min-height: 80px; width: 1%; min-width: 80px; text-align: center; vertical-align: top; padding: 12px; } .icon img { max-width: 60px; padding: 4px; } .icon img[src$='.jpg'], img[src$='.jpeg'], img[src*='.thumbnails'] { // max-width: 48px; border: 1px dotted #bbb; // padding: 4px; background: #f9f9f9; } td.content { padding-left: 12px; vertical-align: top; } #hilight { background-color: #ffee66; color: #000000; padding-left: 2px; padding-right: 2px; margin-left: -2px; margin-right: -2px; } .name { font-size: 1.3em; font-weight: bold; color: black; } .date { font-size: 1em; color: black; margin-bottom: 0.6em; margin-top: 0.2em; margin-left:16px; } .snippet { font-size: 1em; color: gray; margin-left: 16px; } .url { font-size: 1em; color: #008200; margin-left: 16px; } ul { margin-left: 16px; padding: 0px; clear: both; } .actions { font-size: 1em; } .actions li { float: left; display: block; vertical-align: middle; padding: 0; padding-left: 20px; padding-right: 12px; background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/navigation/stock_right.png) no-repeat; min-height: 16px; -moz-opacity: 0.5; } tr:hover .actions li { -moz-opacity: 1.0;} #phone { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/generic/stock_landline-phone.png) no-repeat; } #email { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/net/stock_mail.png) no-repeat; } #email-forward { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/net/stock_mail-forward.png) no-repeat; } #email-reply { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/net/stock_mail-reply.png) no-repeat; } #message { background: url(file:///opt/gnome/share/icons/hicolor/16x16/apps/im-yahoo.png) no-repeat; } #reveal { background: url(file:///opt/gnome/share/icons/hicolor/16x16/stock/io/stock_open.png) no-repeat; } td.footer { text-align: right; border-bottom: solid 1px white; } </style>";