From c5ea6ff7abd6e376ae151c9724d24f6fe156766e Mon Sep 17 00:00:00 2001
From: Michael Krelin <hacker@klever.net>
Date: Tue, 26 Sep 2006 19:21:57 +0000
Subject: Initial import into public repository

git-svn-id: http://svn.klever.net/kin/fireflix/trunk@159 fe716a7a-6dde-0310-88d9-d003556173a8
---
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..a9fb0c7
--- a/dev/null
+++ b/AUTHORS
@@ -0,0 +1,3 @@
+Klever dissected:
+	Michael 'hacker' Krelin <hacker@klever.net>
+	Leonid Ivanov <kamel@klever.net>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..9a21651
--- a/dev/null
+++ b/COPYING
@@ -0,0 +1,19 @@
+Copyright (c) 2006 Klever Group (http://www.klever.net/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
--- a/dev/null
+++ b/ChangeLog
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..913b46a
--- a/dev/null
+++ b/Makefile.am
@@ -0,0 +1,37 @@
+SUBDIRS = content locale
+
+xpi_DATA = \
+	install.rdf \
+	AUTHORS COPYING NEWS
+
+EXTRA_DIST = NEWS NEWS.xml NEWS.xsl chrome.manifest
+
+XPI=${PACKAGE}-${VERSION}.xpi
+
+xpi: ${XPI}
+
+${XPI}: install
+	cd ${xpichromedir} \
+	&& ${ZIP} -r -m ${PACKAGE}.jar */
+	cd ${xpidir} \
+	&& ${ZIP} -r @abs_builddir@/$@ .
+
+install-data-local: ${xpidir}/chrome.manifest
+
+${xpidir}/chrome.manifest: chrome.manifest Makefile
+	sed \
+	 -e 's,^content[[:space:]]\+\([^[:space:]]\+\)[[:space:]]\+\([^[:space:]]\+\)$$,content \1 jar:chrome/${PACKAGE}.jar!/\2,' \
+	 -e 's,^locale[[:space:]]\+\([^[:space:]]\+\)[[:space:]]\+\([^[:space:]]\+\)[[:space:]]\+\([^[:space:]]\+\)$$,locale \1 \2 jar:chrome/${PACKAGE}.jar!/\3,' \
+	 $< >$@
+
+clean-local:
+	rm -rf ${xpidir} ${XPI}
+
+all-local: NEWS
+
+NEWS: NEWS.xsl NEWS.xml
+	${XSLTPROC} -o $@ NEWS.xsl NEWS.xml
+
+mozextptr: ${MOZ_EXT_ID}
+${MOZ_EXT_ID}:
+	echo @abs_srcdir@ >$@
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..d669517
--- a/dev/null
+++ b/NEWS
@@ -0,0 +1,2 @@
+0.0 (September 26th, 2006)
+  - Initial release
diff --git a/NEWS.xml b/NEWS.xml
new file mode 100644
index 0000000..ed82c8a
--- a/dev/null
+++ b/NEWS.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="us-ascii"?>
+<news>
+ <version version="0.0" date="September 26th, 2006">
+  <ni>Initial release</ni>
+ </version>
+</news>
diff --git a/NEWS.xsl b/NEWS.xsl
new file mode 100644
index 0000000..7c71307
--- a/dev/null
+++ b/NEWS.xsl
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="us-ascii"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ >
+ <xsl:output
+  method="text"
+  encoding="us-ascii"
+  media-type="text/plain" />
+
+ <xsl:template match="news">
+  <xsl:apply-templates/>
+ </xsl:template>
+ <xsl:template match="version">
+  <xsl:value-of select="concat(@version,' (',@date,')&#xA;')"/>
+  <xsl:apply-templates/>
+ </xsl:template>
+ <xsl:template match="ni">
+  <xsl:text>  - </xsl:text>
+  <xsl:apply-templates mode="text"/>
+  <xsl:text>&#xA;</xsl:text>
+ </xsl:template>
+ <xsl:template match="*|text()"/>
+ 
+</xsl:stylesheet>
diff --git a/README b/README
new file mode 100644
index 0000000..e69de29
--- a/dev/null
+++ b/README
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..1a4b02a
--- a/dev/null
+++ b/autogen.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+WANT_AUTOMAKE=1.8
+export WANT_AUTOMAKE
+aclocal \
+&& automake -a \
+&& autoconf \
+&& ./configure "$@"
diff --git a/chrome.manifest b/chrome.manifest
new file mode 100644
index 0000000..3792a2f
--- a/dev/null
+++ b/chrome.manifest
@@ -0,0 +1,5 @@
+content fireflix content/
+
+overlay chrome://browser/content/browser.xul chrome://fireflix/content/browser.xul
+
+locale fireflix en-US locale/en-US/
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..b76ae0f
--- a/dev/null
+++ b/configure.ac
@@ -0,0 +1,28 @@
+AC_INIT([fireflix], [0.0], [fireflix-bugs@klever.net])
+AC_CONFIG_SRCDIR([install.rdf.in])
+AM_INIT_AUTOMAKE([dist-bzip2])
+AC_SUBST([MOZ_EXT_ID],[{4269f719-86de-4668-b8ad-04752c23a69e}])
+
+AC_PATH_PROG([ZIP],[zip],[false])
+if test "${ZIP}" = "false" ; then
+ AC_MSG_ERROR([zip is required to produce packaged extension])
+fi
+AC_PATH_PROG([XSLTPROC],[xsltproc],[true])
+
+AC_SUBST([xpidir],[\${top_builddir}/xpi])
+AC_SUBST([xpichromedir],[\${xpidir}/chrome])
+AC_SUBST([xpichromecontentdir],[\${xpichromedir}/content])
+AC_SUBST([xpichromelocaledir],[\${xpichromedir}/locale])
+
+COPYING="`sed -e 's/\\"/\\&quot;/g' -e 's,$,<br/>,g' COPYING|tr '\n' ' '`"
+AC_SUBST([COPYING])
+
+AC_CONFIG_FILES([
+ Makefile
+  install.rdf
+  update.rdf
+ content/Makefile
+  content/autoconf.dtd
+ locale/Makefile
+])
+AC_OUTPUT
diff --git a/content/Makefile.am b/content/Makefile.am
new file mode 100644
index 0000000..8548400
--- a/dev/null
+++ b/content/Makefile.am
@@ -0,0 +1,22 @@
+xpichromecontent_DATA = \
+	autoconf.dtd \
+	browser.xul about.xul fireflix-panel.xul photoset-props.xul \
+	generated-content.xul \
+	photoset-props.js fireflix.js flickr.js md5.js \
+	generated-content.js \
+	fireflix.css \
+	background.jpeg
+
+sized_icons = \
+	$(addsuffix .png, \
+	 fireflix \
+	)
+
+nobase_xpichromecontent_DATA = \
+	$(addprefix icons/, \
+	 $(addprefix 16x16/,${sized_icons}) \
+	 $(addprefix 32x32/,${sized_icons}) \
+	)
+
+EXTRA_DIST = \
+	${xpichromecontent_DATA} ${nobase_xpichromecontent_DATA}
diff --git a/content/about.xul b/content/about.xul
new file mode 100644
index 0000000..1cb13d4
--- a/dev/null
+++ b/content/about.xul
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="fireflix.css" type="text/css"?>
+<!DOCTYPE window SYSTEM "chrome://fireflix/locale/fireflix.dtd">
+<window
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  id="aboutFireflix" title="&aboutFireflix;"
+  orient="vertical"
+  width="400" height="200"
+  >
+
+ <hbox class="about wholething" flex="1">
+  <hbox flex="1" class="insides">
+   <vbox>
+    <spacer flex="1"/>
+    <image src="icons/32x32/fireflix.png" width="32" height="32" />
+    <spacer flex="1"/>
+   </vbox>
+   <groupbox flex="1" class="text" orient="vertical">
+    <spacer flex="2"/>
+    <hbox>
+     <spacer flex="1"/>
+     <label value="Fireflix &autoconf.version;" class="title"/>
+     <spacer flex="1"/>
+    </hbox>
+    <spacer flex="1"/>
+    <hbox>
+     <label value="Copyright © 2006 "/>
+     <label value="Klever Group" class="link"
+      onclick="window.openDialog('chrome://browser/content/browser.xul','_blank','chrome,all,dialog=no','http://www.klever.net/',null,null)" />
+    </hbox>
+    <spacer flex="2"/>
+   </groupbox>
+   <vbox>
+    <spacer flex="10" />
+    <button label="&about.license.label;" tooltiptext="&about.license.tip;"
+    oncommand="window.openDialog('chrome://fireflix/content/copying.xul','copying')" />
+    <spacer flex="2" />
+    <button label="OK" oncommand="window.close()"/>
+    <spacer flex="1" />
+   </vbox>
+  </hbox>
+ </hbox>
+
+</window>
diff --git a/content/autoconf.dtd.in b/content/autoconf.dtd.in
new file mode 100644
index 0000000..e4ad217
--- a/dev/null
+++ b/content/autoconf.dtd.in
@@ -0,0 +1,3 @@
+<!ENTITY autoconf.version "@VERSION@">
+<!ENTITY autoconf.package "@PACKAGE@">
+<!ENTITY autoconf.copying "@COPYING@">
diff --git a/content/background.jpeg b/content/background.jpeg
new file mode 100644
index 0000000..43684f7
--- a/dev/null
+++ b/content/background.jpeg
Binary files differ
diff --git a/content/browser.xul b/content/browser.xul
new file mode 100644
index 0000000..aaff443
--- a/dev/null
+++ b/content/browser.xul
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!DOCTYPE overlay SYSTEM "chrome://fireflix/locale/fireflix.dtd">
+<overlay id="fireflixOverlay"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+
+ <menupopup id="viewSidebarMenu">
+  <menuitem observes="viewFireflixSidebar" />
+ </menupopup>
+
+ <broadcasterset id="mainBroadcasterSet">
+  <broadcaster id="viewFireflixSidebar"
+    autoCheck="false" label="&browser.sidebar.label;"
+    type="checkbox" group="sidebar"
+    sidebarurl="chrome://fireflix/content/fireflix-panel.xul"
+    sidebartitle="&browser.sidebar.title;"
+    oncommand="toggleSidebar('viewFireflixSidebar')" />
+ </broadcasterset>
+</overlay>
+
diff --git a/content/copying.xul b/content/copying.xul
new file mode 100644
index 0000000..179dca5
--- a/dev/null
+++ b/content/copying.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="fireflix.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://fireflix/locale/fireflix.dtd">
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="copying" title="&copying.title;" orient="vertical"
+ buttons="accept">
+
+ <div xmlns="http://www.w3.org/1999/xhtml">
+  &autoconf.copying;
+ </div>
+
+</dialog>
diff --git a/content/fireflix-panel.xul b/content/fireflix-panel.xul
new file mode 100644
index 0000000..9953761
--- a/dev/null
+++ b/content/fireflix-panel.xul
@@ -0,0 +1,252 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="fireflix.css" type="text/css"?>
+<!DOCTYPE page SYSTEM "chrome://fireflix/locale/fireflix.dtd">
+<page
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="fireflixwindow" title="Fireflix"
+ onload="fireflix.init()"
+ orient="vertical"
+ ondragover="nsDragAndDrop.dragOver(event,fireflix.uploadObserver)"
+ ondragdrop="nsDragAndDrop.drop(event,fireflix.uploadObserver)"
+ >
+
+ <script src="chrome://global/content/nsDragAndDrop.js"/>
+ <script src="chrome://global/content/nsTransferable.js"/>
+ <script type="application/x-javascript" src="md5.js" />
+ <script type="application/x-javascript" src="flickr.js" />
+ <script type="application/x-javascript" src="fireflix.js" />
+
+ <stringbundleset>
+  <stringbundle id="loc_strings" src="chrome://fireflix/locale/fireflix.properties" />
+ </stringbundleset>
+
+ <commandset id="cmdset_search">
+  <command id="cmd_search" label="&panel.search.cmd_search.label;"
+  oncommand="fireflix.foundphotos.search_photos()"/>
+  <command id="cmd_search_open" label="&panel.search.cmd_search_open.label;"
+  oncommand="fireflix.foundphotos.on_cmd_open(event)" />
+ </commandset>
+
+ <commandset id="cmdset_sets">
+  <command id="cmd_refresh_sets" label="&panel.sets.cmd_refresh_sets;"
+  oncommand="fireflix.on_refresh_sets()" />
+  <command id="cmd_set_props" label="&panel.sets.cmd_properties;"
+  oncommand="fireflix.on_set_props()" disabled="true" />
+ </commandset>
+
+ <popupset>
+  <popup id="sets_menu">
+   <menuitem command="cmd_set_props"/>
+   <menuitem command="cmd_refresh_sets"/>
+   <menuseparator/>
+   <menu label="&panel.sets.generate_html;" id="sets_html_menu"/>
+  </popup>
+ </popupset>
+
+ <commandset id="cmdset_uploads">
+  <command id="cmd_uploads_clear" label="&panel.uploads.clear.label;"
+  oncommand="fireflix.uploads.on_clear()" />
+  <command id="cmd_uploads_upload" label="&panel.uploads.upload.label;"
+  oncommand="fireflix.uploads.on_upload()" />
+  <command id="cmd_uploads_remove" label="&panel.uploads.remove.label;"
+  oncommand="fireflix.uploads.on_remove()" />
+  <command id="cmd_uploads_add" label="&panel.uploads.add.label;"
+  oncommand="fireflix.uploads.on_add()" />
+ </commandset>
+
+ <popupset>
+  <popup id="uploads_menu">
+   <menuitem command="cmd_uploads_add"/>
+   <menuitem command="cmd_uploads_clear"/>
+   <menuitem command="cmd_uploads_remove"/>
+   <menuseparator/>
+   <menuitem command="cmd_uploads_upload"/>
+   <menuseparator/>
+   <menu label="&panel.uploads.generate_html;" id="uploads_html_menu"/>
+  </popup>
+ </popupset>
+
+ <vbox class="wholething" flex="1">
+
+  <groupbox>
+   <caption label="&panel.auth_info;"/>
+   <hbox>
+    <label id="auth_info" value="&panel.no_auth_info;" flex="1" disabled="true"/>
+    <button id="b_auth" label="&panel.auth_button;" oncommand="fireflix.on_auth()"/>
+    <button id="b_auth_done" label="&panel.auth_complete_button;" hidden="true"
+    oncommand="fireflix.on_auth_done()"/>
+    <button label="&panel.flickr_button.label;"
+    tooltiptext="&panel.flickr_button.tip;"
+    oncommand="fireflix.openTab('http://www.flickr.com/')" />
+   </hbox>
+  </groupbox>
+
+  <tabbox flex="1" id="fireflix_tabs">
+
+   <tabs>
+    <tab label="&panel.tabs.search;"/>
+    <tab label="&panel.tabs.sets;"/>
+    <tab label="&panel.tabs.tags;" hidden="true"/> <!-- TODO: -->
+    <tab id="tab_upload" label="&panel.tabs.upload;"/>
+   </tabs>
+
+   <tabpanels flex="1">
+
+    <tabpanel id="tabpanel_search" flex="1">
+     <vbox flex="1">
+      <groupbox class="search_params" orient="vertical" onkeypress="if(event.keyCode==event.DOM_VK_RETURN) fireflix.foundphotos.search_photos()">
+       <hbox>
+	<label control="search_for" value="&panel.search.search_for.label;"
+	accesskey="s"/>
+	<textbox id="search_for" flex="1"/>
+       </hbox>
+       <hbox>
+        <checkbox id="search_tags" label="&panel.search.mode.tagsonly.label;"
+	tooltiptext="&panel.search.mode.tagsonly.tip;" checked="false"
+	accesskey="t" />
+	<checkbox id="search_mine" label="&panel.search.mode.mine.label;" checked="true" accesskey="m"/>
+	<spacer flex="1"/>
+	<button command="cmd_search"/>
+       </hbox>
+      </groupbox>
+      <tree id="searchresults" rows="4" flex="1"
+      onselect="fireflix.foundphotos.on_select()"
+      ondblclick="fireflix.foundphotos.on_cmd_open(event)"
+      onkeypress="if(event.keyCode==event.DOM_VK_RETURN)
+      fireflix.foundphotos.on_cmd_open(event)">
+       <treecols>
+        <treecol id="sr_title" label="&panel.search.col.title.label;" flex="2" crop="end" align="start" />
+       </treecols>
+       <treechildren/>
+      </tree>
+      <groupbox id="searchresult_props" orient="horizontal" hidden="true">
+       <vbox width="100" pack="center">
+        <hbox pack="center">
+         <image id="search_photo"/>
+	</hbox>
+       </vbox>
+       <vbox flex="1">
+        <label id="searchresult_title"/>
+	<textbox flex="1" multiline="true" class="plain" readonly="true" id="searchresult_description"/>
+	<hbox pack="end">
+	 <button command="cmd_search_open"/>
+	</hbox>
+       </vbox>
+      </groupbox>
+     </vbox>
+    </tabpanel>
+
+    <tabpanel id="tabpanel_sets" flex="1"
+     onkeypress="if(event.keyCode==event.DOM_VK_RETURN)
+     document.getElementById('setphotos').focus()">
+     <vbox flex="1">
+      <tree id="setslist" rows="4" onselect="fireflix.photosets.on_select()"
+      flex="1" context="sets_menu"
+      >
+       <treecols>
+	<treecol id="sl_name" label="&panel.sets.name.label;" flex="4" crop="end" align="start" tooltiptext="&panel.sets.name.tip;"/>
+	<splitter class="tree-splitter" />
+	<treecol id="sl_photos" label="&panel.sets.photos.label;" flex="1" align="end" tooltiptext="&panel.sets.photos.tip;" />
+       </treecols>
+       <treechildren/>
+      </tree>
+      <hbox>
+       <button command="cmd_refresh_sets" />
+       <button command="cmd_set_props" />
+      </hbox>
+      <tree id="setphotos" rows="4" onselect="fireflix.photoset.on_select()"
+      flex="1">
+       <treecols>
+	<treecol id="sp_title" label="&panel.setphotos.title.label;" flex="1" crop="end" align="start" tooltiptext="&panel.setphotos.title.tip;" />
+	<splitter class="tree-splitter" />
+	<treecol id="sp_taken" label="&panel.setphotos.taken.label;" crop="end" align="start" tooltiptext="&panel.setphotos.taken.tip;" hidden="true" />
+	<treecol id="sp_upload" label="&panel.setphotos.upload.label;" crop="end" align="start" tooltiptext="&panel.setphotos.upload.tip;" hidden="true" />
+       </treecols>
+       <treechildren/>
+      </tree>
+      <groupbox id="set_photo_props" orient="horizontal">
+       <vbox width="100" pack="center">
+        <hbox pack="center">
+	 <image id="set_photo" hidden="true"/>
+	</hbox>
+       </vbox>
+       <spacer flex="1"/>
+      </groupbox>
+     </vbox>
+    </tabpanel>
+
+    <tabpanel id="tabpanel_tags">
+     <listbox id="tagslist" rows="8" flex="1">
+      <listhead>
+       <listheader label="&panel.tagslist.tag.label;"/>
+      </listhead>
+      <listcols>
+       <listcol flex="1"/>
+      </listcols>
+     </listbox>
+    </tabpanel>
+
+    <tabpanel id="tabpanel_upload">
+     <vbox flex="1">
+      <tree id="uploadlist" rows="8" flex="1"
+      onselect="fireflix.uploads.selectionChanged()"
+      context="uploads_menu">
+       <treecols>
+	<treecol id="up_file" label="&panel.uploadlist.file.label;" flex="4" crop="start" align="start"/>
+	<splitter class="tree-splitter" />
+	<treecol id="up_title" label="&panel.uploadlist.title.label;" flex="5" crop="end" align="start" />
+	<splitter class="tree-splitter" />
+	<treecol id="up_status" label="&panel.uploadlist.status.label;" flex="1" crop="end" align="start" />
+       </treecols>
+       <treechildren/>
+      </tree>
+      <progressmeter id="upload_progress" mode="undetermined" hidden="true" />
+      <groupbox id="upload_file_props" orient="horizontal" hidden="true">
+       <image id="upload_file_preview" width="100" height="100" />
+       <grid flex="1">
+        <columns>
+	 <column/>
+	 <column flex="1"/>
+	</columns>
+	<rows>
+	 <row>
+	  <label control="upload_filename"
+	  value="&panel.upload_props.filename.label;" />
+	  <textbox id="upload_filename"
+	  oninput="fireflix.uploads.propsToSel('filename')"/>
+	 </row>
+	 <row>
+	  <label control="upload_title" value="&panel.upload_props.title.label;" />
+	  <textbox id="upload_title"
+	  oninput="fireflix.uploads.propsToSel('title')"/>
+	 </row>
+	 <row>
+	  <label control="uplod_tags" value="&panel.upload_props.tags.label;" />
+	  <textbox id="upload_tags"
+	  oninput="fireflix.uploads.propsToSel('tags')"/>
+	 </row>
+	 <!-- TODO: description, public, friend, family -->
+	</rows>
+       </grid>
+      </groupbox>
+      <hbox>
+       <button command="cmd_uploads_add" />
+       <spacer flex="1"/>
+       <button command="cmd_uploads_remove" />
+       <spacer flex="1"/>
+       <button command="cmd_uploads_clear" />
+      </hbox>
+      <hbox pack="center">
+       <button command="cmd_uploads_upload" flex="1"/>
+      </hbox>
+     </vbox>
+    </tabpanel>
+
+   </tabpanels>
+
+  </tabbox>
+
+ </vbox>
+
+</page>
diff --git a/content/fireflix.css b/content/fireflix.css
new file mode 100644
index 0000000..188f48e
--- a/dev/null
+++ b/content/fireflix.css
@@ -0,0 +1,90 @@
+.generated.wholething,
+.about.wholething,
+tabbox, tabpanels, tabpanel {
+ background: url("background.jpeg");
+}
+tabpanels {
+ padding: 0px;
+}
+
+tree {
+ margin-top: 2px;
+ background: rgb(12,167,0);
+ color: rgb(255,255,0);
+ font-size: 90%;
+}
+tree treechildren { /* for windows */
+ background: rgb(12,167,0);
+}
+
+tree#uploadlist treechildren::-moz-tree-cell-text(pending) {
+}
+tree#uploadlist treechildren::-moz-tree-cell-text(completed) {
+ color: white;
+}
+tree#uploadlist treechildren::-moz-tree-row(failed) {
+ background: yellow;
+}
+tree#uploadlist treechildren::-moz-tree-cell-text(failed) {
+ color: red;
+}
+tree#uploadlist treechildren::-moz-tree-cell-text(uploading) {
+ font-weight: bold;
+}
+
+groupbox#searchresult_props,
+groupbox.search_params,
+groupbox#upload_file_props,
+groupbox#set_props,
+groupbox#set_photo_props {
+ background: white;
+}
+
+groupbox#upload_file_props label {
+ text-align: right;
+}
+
+image#set_photo, image#set_primary {
+ border: black 1px solid;
+}
+
+.about .insides {
+ margin: 1ex;
+}
+.about .text {
+ border: yellow solid 1px;
+ background: green;
+}
+.about .title {
+ font-size: 300%;
+ font-weight: bold;
+ color: yellow;
+}
+.about .link {
+ text-decoration: underline;
+ color: white;
+ cursor: pointer;
+}
+
+menuitem.menuhead {
+ background: gray;
+ color: black;
+ font-weight: bold;
+}
+
+label#searchresult_description {
+ font-weight: bold;
+}
+textbox#searchresult_description {
+ padding: 1px 3px !important;
+ background: white;
+}
+
+#copying div {
+ margin: 1ex 1em;
+ font-family: courier, monospace;
+ font-size: 9pt;
+ padding: 2px;
+ border: dotted 1px gray;
+ background: white;
+}
diff --git a/content/fireflix.js b/content/fireflix.js
new file mode 100644
index 0000000..9518480
--- a/dev/null
+++ b/content/fireflix.js
@@ -0,0 +1,878 @@
+function splitascii(s) {
+ var rv='';
+ for(var i=0;i<s.length;++i) {
+  var w = s.charCodeAt(i);
+  rv += String.fromCharCode(
+   w&0xff, (w>>8)&0xff );
+ }
+ return rv;
+}
+
+
+var fireflix = {
+ flickr: new Flickr(),
+ init: function() {
+  this.loc_strings = document.getElementById('loc_strings');
+  this.build_menus();
+  this.cmd_set_props = document.getElementById('cmd_set_props');
+  this.foundphotos.init(this);
+  this.photosets.init(this);
+  this.photoset.init(this);
+  this.uploads.init(this);
+  this.uploadObserver.init(this);
+  this.flickr.api_key = '9c43cd66947a57e6f29db1a9da3f72e3';
+  this.flickr.api_shs = '9c33c9e2f0f0cfd5';
+  this.flickr.prefs_root = 'net.klever.kin.fireflix';
+  this.flickr.load_token();
+  document.getElementById('setslist').view = this.photosets;
+  document.getElementById('setphotos').view = this.photoset;
+  document.getElementById('uploadlist').view = this.uploads;
+  this.flickr.no_auth_info_label = document.getElementById('auth_info').value;
+  if(this.flickr.token) {
+   this.refresh_stuff();
+   document.getElementById('auth_info').value =
+    this.flickr.user.fullname+' ['+this.flickr.user.username+']';
+   document.getElementById('auth_info').disabled = false;
+   document.getElementById('b_auth').hidden = true;
+  }
+ },
+ on_auth: function() {
+  var _this = this;
+  this.flickr.authorize_0(
+   function() {
+    document.getElementById('b_auth').hidden = true;
+    document.getElementById('b_auth_done').hidden = false;
+   }, function(x,s,c,m) {
+    _this.flickr_failure(x,s,c,m);
+   }
+  );
+ },
+ on_auth_done: function() {
+  document.getElementById('b_auth_done').hidden = true;
+  var _this = this;
+  this.flickr.authorize_1(
+   function() {
+    _this.flickr.save_token();
+    _this.refresh_stuff();
+    document.getElementById('auth_info').value =
+     _this.flickr.user.fullname+' ['+_this.flickr.user.username+']';
+    document.getElementById('auth_info').disabled = false;
+   }, function(x,s,c,m) {
+    document.getElementById('b_auth').hidden = false;
+    _this.flickr_failure(x,s,c,m);
+   }
+  );
+ },
+
+ refresh_sets: function() { this.photosets.refresh_sets(); },
+ refresh_stuff: function() {
+  this.refresh_sets();
+  this.refresh_user_tags();
+ },
+
+ /* photoset treeview */
+ photoset: {
+  photos: new Array(),
+  fireflix: null,
+  init: function(f) {
+   this.fireflix = f;
+  },
+  rowCount: 0,
+  getCellText: function(r,c) {
+   var p = this.photos[r];
+   if(c.id=='sp_title') return p.title;
+   if(c.id=='sp_taken') return p.datetaken;
+   if(c.id=='sp_upload') return p.dateupload; /* TODO: unixtime conversion */
+   return c.id;
+  },
+  setTree: function(t) { this.tree = t },
+  isContainer: function(r) { return false; },
+  isSeparator: function(r) { return false; },
+  isSorted: function(r) { return false; },
+  getLevel: function(r) { return 0; },
+  getImageSrc: function(r,c) { return null },
+  getRowProperties: function(r,p) {},
+  getCellProperties: function(cid,cel,p) {},
+  getColumnProperties: function(cid,cel,p) { },
+  cycleHeader: function(cid,e) { },
+  getParentIndex: function(r) { return -1; },
+  drop: function(r,o) { },
+  canDropBeforeAfter: function(r,b) { return false },
+
+  importXPR: function(xp) {
+   this.tree.beginUpdateBatch();
+   this.photos = new Array();
+   var n; while(n=xp.iterateNext()) {
+    this.photos.push(new Photo(n));
+   }
+   this.rowCount = this.photos.length;
+   this.tree.endUpdateBatch();
+  },
+  load_photos: function(psid) {
+   var _this = this;
+   this.fireflix.flickr.api_call(
+    {
+     method: 'flickr.photosets.getPhotos',
+     auth_token: 'default',
+     photoset_id: psid,
+     extras: 'license,date_upload,date_taken,owner_name,icon_server,original_format,last_update'
+    }, function(xr) {
+     var x = xr.responseXML;
+     var xp = x.evaluate(
+      '/rsp/photoset/photo', x, null,
+      XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
+     _this.importXPR(xp);
+    }, function(x,s,c,m) {
+     _this.fireflix.flickr_failure(x,s,c,m);
+    }
+   );
+  },
+  on_select: function() {
+   if(this.selection.count==1) {
+    var p = this.photos[this.selection.currentIndex];
+    document.getElementById('set_photo').src =
+     this.fireflix.flickr.get_photo_url(p.server,p.id,p.secret,'t');
+    document.getElementById('set_photo').hidden = false;
+   }else{
+    document.getElementById('set_photo').hidden = true;
+   }
+  }
+ },
+
+ /* photosets treeview */
+ photosets: {
+  sets: new Array(),
+  fireflix: null,
+  init: function(f) {
+   this.fireflix = f;
+  },
+  rowCount: 0,
+  getCellText: function(r,c) {
+   var s = this.sets[r];
+   if(c.id=='sl_name') return s.title;
+   if(c.id=='sl_photos') return s.photos;
+   return c.id;
+  },
+  setTree: function(t) { this.tree = t },
+  isContainer: function(r) { return false; },
+  isSeparator: function(r) { return false; },
+  isSorted: function() { return false; },
+  getLevel: function(r) { return 0; },
+  getImageSrc: function(r,c) { return null },
+  getRowProperties: function(r,p) {},
+  getCellProperties: function(cid,cel,p) { },
+  getColumnProperties: function(cid,cel,p) { },
+  cycleHeader: function(cid,e) { },
+  getParentIndex: function(r) { return -1; },
+  drop: function(r,o) { },
+  canDropBeforeAfter: function(r,b) { return false },
+
+  importXPR: function(xp) {
+   this.tree.beginUpdateBatch();
+   this.sets = new Array();
+   var n; while(n=xp.iterateNext()) {
+    this.sets.push(new Photoset(n));
+   }
+   this.rowCount = this.sets.length;
+   this.tree.endUpdateBatch();
+  },
+  refresh_sets: function() {
+   var _this = this;
+   this.fireflix.flickr.api_call(
+    {
+     method: 'flickr.photosets.getList',
+     auth_token: 'default'
+    }, function(xr) {
+     var x = xr.responseXML;
+     var xp = x.evaluate(
+      '/rsp/photosets/photoset', x, null,
+      XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
+     _this.importXPR(xp);
+    }, function(x,s,c,m) {
+     _this.fireflix.flickr_failure(x,s,c,m);
+    }
+   );
+  },
+  on_select: function() {
+   if(this.selection.count==1) {
+    this.fireflix.cmd_set_props.setAttribute('disabled','false');
+    var s = this.sets[this.selection.currentIndex];
+    this.fireflix.photoset.load_photos(s.id);
+   }else{
+    this.fireflix.cmd_set_props.setAttribute('disabled','true');
+   }
+  }
+ },
+
+ refresh_user_tags: function() {
+  var lb = document.getElementById('tagslist');
+  var _this = this;
+  this.flickr.api_call(
+   {
+    method: 'flickr.tags.getListUser',
+    auth_token: 'default',
+   }, function(xr) {
+    var x = xr.responseXML;
+    var xp = x.evaluate(
+     '/rsp/who/tags/tag', x, null,
+     XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
+    // TODO: clear list
+    var n; while(n=xp.iterateNext()) {
+     lb.appendItem(n.firstChild.nodeValue);
+    }
+   }, function(x,s,c,m) {
+    _this.flickr_failure(x,s,c,m);
+   }
+  );
+ },
+
+ uploadObserver: {
+  fireflix: null,
+  init: function(f) {
+   this.fireflix = f;
+  },
+  getSupportedFlavours: function() {
+   var rv = new FlavourSet();
+   rv.appendFlavour('application/x-moz-file','nsIFile');
+   rv.appendFlavour('application/x-moz-url');
+   rv.appendFlavour('text/uri-list');
+   rv.appendFlavour('text/unicode');
+   return rv;
+  },
+  canHandleMultipleItems: true,
+  onDragOver: function(ev,fl,sess) {
+   return true;
+  },
+  onDrop: function(ev,dd,s) {
+   var ldf = null;
+   for(var i in dd.dataList) {
+    var di = dd.dataList[i];
+    var dif = di.first;
+    if(
+     ldf==null
+     || ldf.flavour.contentType!=dif.flavour.contentType
+     || ldf.contentLength!=dif.contentLength
+     || ldf.data!=dif.data )
+     this.drop_item(ev,di,s);
+    ldf = dif;
+   }
+  },
+  drop_item: function(ev,di,s) {
+   var d = di.first;
+   switch(d.flavour.contentType) {
+    case 'text/unicode':
+     this.drop_urilist(ev,d.data,s);
+     break;
+    case 'application/x-moz-file':
+     this.fireflix.uploads.add(d.data.path);
+     document.getElementById('fireflix_tabs').selectedTab
+      = document.getElementById('tab_upload');
+     break;
+    case 'text/uri-list':
+     // is it ascii or could it be utf8?
+     this.drop_urilist(ev,splitascii(d.data),s);
+     break;
+    default: alert(d.flavour.contentType+':'+d.data); break;
+   };
+  },
+  drop_urilist: function(ev,ul,s) {
+   // TODO: check for being a file?
+   var us = decodeURIComponent(ul).split(/[\r\n]/);
+   for(var ui in us)
+    if(/\S/.test(us[ui]))
+     this.fireflix.uploads.add(us[ui]);
+   document.getElementById('fireflix_tabs').selectedTab
+    = document.getElementById('tab_upload');
+  }
+ },
+
+ uploads: {
+  fireflix: null,
+  init: function(f) {
+   this.fireflix=f;
+   this.upload_filename = document.getElementById('upload_filename');
+   this.upload_title = document.getElementById('upload_title');
+   this.upload_file_preview = document.getElementById('upload_file_preview');
+   this.upload_file_props = document.getElementById('upload_file_props');
+   this.upload_progress = document.getElementById('upload_progress');
+   this.upload_tags = document.getElementById('upload_tags');
+  },
+  files: new Array(),
+  rowCount: 0,
+  getCellText: function(r,c) {
+   var f = this.files[r];
+   if(c.id=='up_file') return f.file;
+   if(c.id=='up_title') return f.title;
+   if(c.id=='up_status') return f.state;
+   return c.id;
+  },
+  setTree: function(t) { this.tree = t },
+  isContainer: function(r) { return false; },
+  isSeparator: function(r) { return false; },
+  isSorted: function(r) { return false; },
+  getLevel: function(r) { return 0; },
+  getImageSrc: function(r,c) { return null },
+  getRowProperties: function(r,p) {
+   try {
+    if(!Components) return;
+   }catch(e) { return }
+   var f = this.files[r];
+   var as = Components.classes['@mozilla.org/atom-service;1'].
+    getService(Components.interfaces.nsIAtomService);
+   p.AppendElement(as.getAtom(f.state));
+  },
+  getCellProperties: function(r,c,p) { this.getRowProperties(r,p); },
+  getColumnProperties: function(c,p) { },
+  cycleHeader: function(cid,e) { },
+  getParentIndex: function(r) { return -1; },
+  drop: function(r,o) { },
+  canDropBeforeAfter: function(r,b) { return false },
+
+  add: function(f) {
+   if(f.indexOf('file:/')==0) {
+    f = f.substr(5);
+    while(f.substr(0,2)=='//') { // XXX: not very performant, is it? ;-)
+     f = f.substr(1);
+    }
+   }
+   var t = f;
+   var ls = t.lastIndexOf('/');
+   if(ls>0) t = t.substr(ls+1);
+   ls = t.lastIndexOf('\\');
+   if(ls>0) t = t.substr(ls+1);
+   var ld = t.lastIndexOf('.');
+   if(ld>0) t = t.substr(0,ld);
+   this.files.push( {
+    file: f,
+    title: t,
+    tags: '',
+    state: 'pending'
+   } );
+   this.rowCount = this.files.length;
+   this.tree.rowCountChanged(this.rowCount-1,1);
+  },
+
+  upload_worker: function() {
+   for(var f in this.files) {
+    if(this.files[f].state=='pending') {
+     var ff = this.files[f];
+     dump('upload '+ff.file+'\n');
+     this.on_file_upload(ff);
+     ff.state='uploading';
+     this.tree.invalidate();
+     var _this = this;
+     this.fireflix.flickr.upload_file(
+      ff.file, { title: ff.title, tags: ff.tags },
+      function(x,p) {
+       ff.photoid = p;
+       _this.batch_ids.push(p);
+       ff.state='completed';
+       _this.tree.invalidate();
+       window.setTimeout(_this.upload_to,0,_this);
+      }, function(x,s,c,m) {
+       ff.state='failed';
+       ff.flickr_errcode = c;
+       ff.flickr_errmsg = m;
+       _this.tree.invalidate();
+       window.setTimeout(_this.upload_to,0,_this);
+      }
+     );
+     return;
+    }
+   }
+   dump('uploading done\n');
+   this.on_finish_upload();
+  },
+  upload_to: function(_this) { _this.upload_worker(); },
+  on_file_upload: function(f) {
+   document.getElementById('cmd_uploads_upload').setAttribute('disabled','true');
+   for(var fi in this.files) {
+    if(this.files[fi].file==f.file) {
+     this.tree.ensureRowIsVisible(fi);
+     this.selection.rangedSelect(fi,fi,false);
+     this.selection.currentIndex = fi;
+     this.selToProps();
+     break;
+    }
+   }
+  },
+  on_finish_upload: function() {
+   if(this.batch_ids.length) {
+    var psn = prompt(this.fireflix.loc_strings.getString('postUploadPhotoset'));
+    if(psn!=null) {
+     var pids = this.batch_ids.join(',');
+     var ppid = this.batch_ids[0];
+     var _this = this;
+     this.fireflix.flickr.api_call(
+      {
+       method: 'flickr.photosets.create',
+       auth_token: 'default',
+       title: psn,
+       primary_photo_id: ppid
+      }, function(x) {
+       var npid =
+        x.responseXML.getElementsByTagName('photoset').item(0).getAttribute('id');
+       _this.fireflix.flickr.api_call(
+        {
+	 method: 'flickr.photosets.editPhotos',
+	 auth_token: 'default',
+	 photoset_id: npid,
+	 primary_photo_id: ppid,
+	 photo_ids: pids
+	}, function(x) {
+	 _this.fireflix.refresh_sets();
+	}, function(x,s,c,m) {
+	 _this.fireflix.flickr_failure(x,s,c,m);
+	}
+       );
+      }, function(x,s,c,m) {
+       _this.fireflix.flickr_failure(x,s,c,m);
+      }
+     );
+    }
+   }
+   this.selection.clearSelection();
+   document.getElementById('cmd_uploads_upload').setAttribute('disabled','false');
+   this.upload_progress.setAttribute('hidden','true');
+  },
+
+  clear_list: function() {
+   this.tree.beginUpdateBatch();
+   this.rowCount = 0;
+   this.files = new Array();
+   this.tree.endUpdateBatch();
+   this.selToProps();
+  },
+  selectionChanged: function() {
+   this.selToProps();
+  },
+  disableProps: function() {
+   this.upload_filename.value='';
+   this.upload_filename.disabled = true;
+   this.upload_title.value='';
+   this.upload_title.disabled = true;
+   this.upload_file_preview.src = null;
+   this.upload_file_props.hidden = true;
+   this.upload_tags.value='';
+   this.upload_tags.disabled = true;
+  },
+  selToProps: function() {
+   if(!this.selection.count) {
+    this.disableProps();
+   }else if(this.selection.count==1) {
+    var f=this.files[this.selection.currentIndex];
+    if(f==null || f.state!='pending') {
+     this.disableProps();
+    }else{
+     this.upload_filename.value = f.file;
+     this.upload_filename.disabled = false;
+     this.upload_title.value = f.title;
+     this.upload_title.disabled = false;
+     this.upload_file_preview.src = 'file:///'+f.file;
+     this.upload_file_props.hidden = false;
+     this.upload_tags.value = f.tags;
+     this.upload_tags.disabled = false;
+    }
+   }else{
+    var ftitle = null; var onetitle = true;
+    var ftags = null; var onetag = true;
+    var fs = 0;
+    for(var ff in this.files) {
+     if(this.selection.isSelected(ff) && this.files[ff].state=='pending' ) {
+      ++fs;
+      if(ftitle==null) {
+       ftitle = this.files[ff].title;
+      }else if(ftitle!=this.files[ff].title) {
+       onetitle = false;
+      }
+      if(ftags==null) {
+       ftags = this.files[ff].tags;
+      }else if(ftags!=this.files[ff].tags) {
+       onetag = false;
+      }
+     }
+    }
+    if(fs) {
+     this.upload_filename.value='';
+     this.upload_filename.disabled = true;
+     if(onetitle)
+      this.upload_title.value = ftitle;
+     this.upload_title.disabled = false;
+     if(onetag)
+      this.upload_tags.value = ftags;
+     this.upload_tags.disabled = false;
+     this.upload_file_preview.src = null;
+     this.upload_file_props.hidden = false;
+    }else
+     this.disableProps();
+   }
+  },
+  propsToSel: function(prop) {
+   if(this.selection.count<=0) return;
+   for(var ff in this.files) {
+    if(this.selection.isSelected(ff) && this.files[ff].state=='pending') {
+     if(prop=='filename')
+      this.files[ff].file = this.upload_filename.value;
+     if(prop=='title')
+      this.files[ff].title = this.upload_title.value;
+     if(prop=='tags')
+      this.files[ff].tags = this.upload_tags.value;
+     this.tree.invalidateRow(ff);
+    }
+   }
+  },
+
+  on_upload: function() {
+   this.selToProps();
+   this.batch_ids = new Array();
+   this.upload_progress.value=0;
+   this.upload_progress.setAttribute('hidden','false');
+   this.upload_worker();
+  },
+  on_clear: function() {
+   this.clear_list();
+  },
+  on_remove: function() {
+   if(this.selection.count) {
+    this.tree.beginUpdateBatch();
+    for(var i=this.files.length-1;i>=0;--i) {
+     if(this.selection.isSelected(i)) {
+      this.files.splice(i,1);
+      this.rowCount--;
+     }
+    }
+    this.tree.endUpdateBatch();
+    this.selection.clearSelection();
+   }
+  },
+  on_add: function() {
+   var ifp = Components.interfaces.nsIFilePicker;
+   var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(ifp);
+   fp.init(window, "Select a File", ifp.modeOpenMultiple);
+   fp.appendFilters(ifp.filterImages);
+   var rv = fp.show();
+   if(rv==ifp.returnOK) {
+    var ff = fp.files;
+    while(ff.hasMoreElements()) {
+     var f = ff.getNext();
+     f.QueryInterface(Components.interfaces.nsIFile);
+     this.add(f.path);
+    }
+   }
+  }
+ },
+
+ on_set_props: function() {
+  var pset = this.photosets.sets[this.photosets.selection.currentIndex];
+  window.openDialog(
+   "chrome://fireflix/content/photoset-props.xul",
+   null, "dependent,modal,dialog,chrome", this,
+   pset );
+  if(pset.dirty) {
+   var _this = this;
+   this.flickr.api_call(
+    {
+     method: 'flickr.photosets.editMeta',
+     auth_token: 'default',
+     photoset_id: pset.id,
+     title: pset.title,
+     description: pset.description
+    }, function(xr) {
+     pset.dirty = false;
+     _this.flickr.api_call(
+      {
+       method: 'flickr.photosets.getPhotos',
+       auth_token: 'default',
+       photoset_id: pset.id
+      }, function(xr) {
+       var x = xr.responseXML;
+       var xp = x.evaluate(
+        '/rsp/photoset/photo', x, null,
+	XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
+       var phids = new Array();
+       var priph = null;
+       var n; while(n=xp.iterateNext()) {
+        var pid = n.getAttribute('id');
+        phids.push( pid );
+	if(pid==pset.primary && n.getAttribute('isprimary')!='1')
+	 priph = pid;
+       }
+       if(priph) {
+	_this.flickr.api_call(
+	 {
+	  method: 'flickr.photosets.editPhotos',
+	  auth_token: 'default',
+	  photoset_id: pset.id,
+	  primary_photo_id: priph,
+	  photo_ids: phids.join(',')
+	 }, function() { }, function(x,s,c,m) { /* flickr.photosets.editPhotos */
+	  _this.flickr_failure(x,s,c,m);
+	 }
+	);
+       }
+      }, function(x,s,c,m) { /* flickr.photosets.getPhotos */
+       _this.flickr_failure(x,s,c,m);
+      }
+     );
+    }, function(x,s,c,m) { /* flickr.photosets.editMeta */
+     _this.flickr_failure(x,s,c,m);
+    }
+   );
+  }
+ },
+ on_refresh_sets: function() {
+  this.refresh_sets();
+ },
+ on_cmd_sets_html: function(csfx,ev) {
+  var uti = csfx.charAt(0); var utl = csfx.charAt(1);
+  var rv = this.build_html(this.photoset.photos,uti,utl);
+  this.popup_content(rv);
+ },
+
+ on_cmd_uploads_html: function(csfx,ev) {
+  var uti = csfx.charAt(0); var utl = csfx.charAt(1);
+  var pids = new Array();
+  for(var f in this.uploads.files) {
+   if(this.uploads.selection.isSelected(f))
+    if(this.uploads.files[f].photoid)
+     pids.push(this.uploads.files[f].photoid);
+  }
+  var pp = this.uploads.rowCount*2; if(pp>500) pp = 500;
+  var _this = this;
+  this.flickr.api_call(
+   {
+    method: 'flickr.photos.search',
+    auth_token: 'default',
+    extras: 'original_format',
+    user_id: 'me',
+    per_page: pp
+   },
+   function(xr) {
+    var x = xr.responseXML;
+    var rv = '';
+    for(var pn in pids) {
+     var p = pids[pn];
+     var pp = new Photo(xp_node('/rsp/photos/photo[@id='+p+']',x));
+     rv += _this.photo_html(pp,uti,utl)+'\n';
+    }
+    _this.popup_content(rv);
+   }, function(x,s,c,m) {
+    _this.flickr_failure(x,s,c,m);
+   }
+  );
+ },
+
+ /*
+  *
+  */
+ foundphotos: {
+  fireflix: null,
+  init: function(f) {
+   this.fireflix = f;
+   this.search_for = document.getElementById('search_for');
+   this.search_tags= document.getElementById('search_tags');
+   this.search_mine = document.getElementById('search_mine');
+   document.getElementById('searchresults').view = this;
+   this.searchresult_props = document.getElementById('searchresult_props');
+   this.search_photo = document.getElementById('search_photo');
+   this.searchresult_title = document.getElementById('searchresult_title');
+   this.searchresult_description = document.getElementById('searchresult_description');
+  },
+  photos: new Array(),
+  rowCount: 0,
+  getCellText: function(r,c) {
+   var p = this.photos[r];
+   if(c.id=='sr_title') return p.title;
+   return c.id;
+  },
+  setTree: function(t) { this.tree = t },
+  isContainer: function(r) { return false },
+  isSeparator: function(r) { return false },
+  isSorted: function(r) { return false },
+  getLevel: function(r) { return 0 },
+  getImageSrc: function(r,c) { return null },
+  getRowProperties: function(r,p) { },
+  getCellProperties: function(cid,cel,p) { },
+  getColumnProperties: function(cid,cel,p) { },
+  cycleHeader: function(cid,e) { },
+  getParentIndex: function(r) { return -1 },
+  drop: function(r,o) { },
+  canDropBeforeAfter: function(r,b) { return false },
+
+  importXPR: function(xp) {
+   this.selection.clearSelection();
+   this.selection.currentIndex = -1;
+   this.searchresult_props.hidden = true;
+   this.tree.beginUpdateBatch();
+   this.photos = new Array();
+   var n; while(n=xp.iterateNext()) {
+    this.photos.push(new Photo(n));
+   }
+   this.rowCount = this.photos.length;
+   this.tree.endUpdateBatch();
+  },
+  search_photos: function() {
+   var pars = {
+    method: 'flickr.photos.search',
+    auth_token: 'default',
+    extras: 'license,date_upload,date_taken,owner_name,icon_server,original_format,last_update,geo'
+   };
+   if(this.search_mine.checked)
+    pars.user_id='me';
+   if(this.search_tags.checked) {
+    pars.tags=this.search_for.value.split(/ +/).join(',');
+   }else{
+    pars.text=this.search_for.value;
+   }
+   var _this = this;
+   this.fireflix.flickr.api_call( pars,
+    function(xr) {
+     var x = xr.responseXML;
+     var xp = x.evaluate(
+      '/rsp/photos/photo', x, null,
+      XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
+     _this.importXPR(xp);
+     _this.on_select();
+    }, function(x,s,c,m) {
+     _this.fireflix.flickr_failure(x,s,c,m);
+    }
+   );
+  },
+  on_select: function() {
+   if(this.selection.currentIndex<0) {
+    this.searchresult_props.hidden = true;
+   }else{
+    var p = this.photos[this.selection.currentIndex];
+    if(!p) {
+     this.searchresult_props.hidden = true;
+    }else{
+     this.search_photo.src = this.fireflix.flickr.make_photo_url(p,'t');
+     this.searchresult_title.value = p.title;
+     this.searchresult_description.value = null;
+     if(p.description==null && p.description==undefined) {
+      var pid = p.id;
+      var ci = this.selection.currentIndex;
+      var _this = this;
+      this.fireflix.flickr.api_call(
+       {
+	method: 'flickr.photos.getInfo',
+	auth_token: 'default',
+	photo_id: p.id,
+	secret: p.secret
+       }, function(xr) {
+	var pp = _this.photos[ci];
+	if(ci==_this.selection.currentIndex && pp.id==pid) {
+	 var n = xp_node('/rsp/photo',xr.responseXML);
+	 pp.fromNode_(n);
+	 _this.searchresult_description.value=pp.description?pp.description:null;
+	}
+       }, function(x,s,c,m) {
+	_this.fireflix.flickr_failure(x,s,c,m);
+       }
+      );
+      this.searchresult_props.hidden = false;
+     }else{
+      this.searchresult_description.value=p.description?p.description:null;
+     }
+    }
+   }
+  },
+  on_cmd_open: function(ev) {
+   if(this.selection.currentIndex<0)
+    return;
+   var p = this.photos[this.selection.currentIndex];
+   if(!p.id)
+    return;
+   this.fireflix.openTab(this.fireflix.flickr.make_photo_url(p,'p'));
+  }
+ },
+
+ photo_html: function(p,i,l) {
+  // TODO: add alt/title when possible
+  var rv = 
+   '<a href="'+this.flickr.make_photo_url(p,l)+'">' +
+   '<img src="'+this.flickr.make_photo_url(p,i)+'" />'+
+   '</a>';
+  return rv;
+ },
+ build_html: function(photos,uti,utl) {
+  var rv = '';
+  for(var i in photos) {
+   var p = photos[i];
+   rv += this.photo_html(p,utl,uti)+'\n';
+  }
+  return rv;
+ },
+
+ popup_content: function(s) {
+  window.openDialog(
+   "chrome://fireflix/content/generated-content.xul",
+   null, "dialog,chrome", this, s );
+ },
+ copy_to_clipboard: function(s) {
+  var ch = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+   .getService(Components.interfaces.nsIClipboardHelper);
+  ch.copyString(s); 
+ },
+ openTab: function(l) {
+  var wm = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(
+   Components.interfaces.nsIWindowMediator );
+  var bw = wm.getMostRecentWindow('navigator:browser');
+  var b = bw.getBrowser();
+  var t = b.addTab(l);
+  b.selectedTab = t;
+ },
+
+ build_menus: function() {
+  this.append_html_menu(
+   document.getElementById('sets_html_menu'),
+   'stm_','m_bop','cmdset_sets','cmd_sets_html'
+  );
+  this.append_html_menu(
+   document.getElementById('uploads_html_menu'),
+   'stm_','m_bop','cmdset_uploads','cmd_uploads_html'
+  );
+  return;
+ },
+ append_html_menu: function(m,imgt,lnkt,csid,cpfx) {
+  var mp = m.appendChild(document.createElement('menupopup'));
+  var t;
+  t=mp.appendChild(document.createElement('menuitem'));
+  t.setAttribute('label',this.loc_strings.getString('menutitle_Images'));
+  t.setAttribute('class','menuhead');t.setAttribute('disabled','true');
+  mp.appendChild(document.createElement('menuseparator'));
+  var cs = document.getElementById(csid);
+  for(var iti=0;iti<imgt.length;++iti) {
+   t = mp.appendChild(document.createElement('menu'));
+   t.setAttribute('label',this.loc_strings.getString('urltype_'+imgt.charAt(iti)));
+   var smp = t.appendChild(document.createElement('menupopup'));
+   t=smp.appendChild(document.createElement('menuitem'));
+   t.setAttribute('label',this.loc_strings.getString('menutitle_Links'));
+   t.setAttribute('class','menuhead');t.setAttribute('disabled','true');
+   smp.appendChild(document.createElement('menuseparator'));
+   for(var lti=0;lti<lnkt.length;++lti) {
+    var csfx = imgt.charAt(iti)+lnkt.charAt(lti);
+    t=smp.appendChild(document.createElement('menuitem'));
+    t.setAttribute('label',this.loc_strings.getString('urltype_'+lnkt.charAt(lti)));
+    t.setAttribute('command',cpfx+'_'+csfx);
+    t=cs.appendChild(document.createElement('command'));
+    t.setAttribute('id',cpfx+'_'+csfx);
+    t.setAttribute('oncommand','fireflix.on_'+cpfx+"('"+csfx+"',event)");
+   }
+  }
+  return mp;
+ },
+
+ flickr_failure: function(x,s,c,m) {
+  if(c==98) { // Invalid auth token
+   this.flickr.reset_token();
+   document.getElementById('auth_info').value = this.no_auth_info_label;
+   document.getElementById('auth_info').disabled = true;
+   document.getElementById('b_auth').hidden = false;
+   return;
+  }
+  // TODO: is that beauty
+  alert('flickr api call failed\n'+c+' '+m);
+ }
+
+};
diff --git a/content/flickr.js b/content/flickr.js
new file mode 100644
index 0000000..3554796
--- a/dev/null
+++ b/content/flickr.js
@@ -0,0 +1,403 @@
+/*
+ * Photoset
+ */
+
+function Photoset(s) {
+ if(s instanceof Photoset) {
+   for(var p in s) this[p]=s[p];
+ }else
+  this.fromNode(s);
+}
+Photoset.prototype = {
+ id: null,
+ primary: null,
+ secret: null,
+ server: null,
+ photos: null,
+ title: null,
+ description: null,
+ fromNode: function(n) {
+  this.id = n.getAttribute('id');
+  this.primary = n.getAttribute('primary');
+  this.secret = n.getAttribute('secret');
+  this.server = n.getAttribute('server');
+  this.photos = n.getAttribute('photos');
+  this.title = n.getElementsByTagName('title').item(0).firstChild.nodeValue;
+  this.description = n.getElementsByTagName('description').item(0).firstChild;
+  if(this.description) this.description = this.description.nodeValue;
+ }
+};
+
+/*
+ * Photo
+ */
+function Photo(s) {
+ if(s instanceof Photo) {
+  for(var p in s) this[p]=s[p];
+ }else
+  this.fromNode(s);
+}
+Photo.prototype = {
+ id: null, secret: null,
+ server: null,
+ title: null,
+ isprimary: null,
+ license: null,
+ dateupload: null, datetaken: null, datetakengranularity: null,
+ ownername: null,
+ iconserver: null,
+ originalformat: null,
+ lastupdate: null,
+ fromNode: function(n) {
+  this.id = n.getAttribute('id'); this.secret = n.getAttribute('secret');
+  this.server = n.getAttribute('server');
+  this.title = n.getAttribute('title');
+  this.isprimary = n.getAttribute('isprimary');
+  this.license = n.getAttribute('license');
+  this.dateupload = n.getAttribute('dateupload');
+  this.datetaken = n.getAttribute('datetaken'); this.datetakengranularity = n.getAttribute('datetakengranularity');
+  this.ownername = n.getAttribute('ownername');
+  this.iconserver = n.getAttribute('iconserver');
+  this.originalformat = n.getAttribute('originalformat');
+  this.lastupdate = n.getAttribute('lastupdate');
+ },
+ fromNode_: function(n) {
+  var t;
+  // TODO: @rotation @isfavorite
+  this.owner = {};
+  t = n.getElementsByTagName('owner').item(0);
+  if(t) {
+   this.owner.nsid=t.getAttribute('nsid');
+   this.owner.username=t.getAttribute('username');
+   this.owner.realname=t.getAttribute('realname');
+   this.owner.location=t.getAttribute.location;
+  }
+  t = n.getElementsByTagName('description').item(0);
+  if(t && t.firstChild) {
+   this.description = t.firstChild.nodeValue;
+  }
+  // TODO: visibility/@ispublic visibility/@isfriend visibility/@isfamily 
+  // TODO: dates/@posted dates/@taken dates/@takengranularity dates/@lastupdate
+  // TODO: permissions/@permcomment permsiions/@permaddmeta
+  // TODO: editability/@canaddcomment editability/@canaddmeta
+  // TODO: comments
+  // TODO: notes/note/@id notes/note/@author notes/note/@authorname
+  // TODO: notes/note/@x notes/note/@y notes/note/@w notes/note/@h
+  // TODO: notes/note
+  // TODO: tags/tag/@id tags/tag/@author tags/tag/@raw tags/tag
+  // TODO: urls/url/@type urls/url
+ }
+};
+
+function toutf8(ucode) {
+ var rv = '';
+ for(var i=0;i<ucode.length;++i) {
+  var cc = ucode.charCodeAt(i);
+  if(cc<=0x7F)
+   rv += ucode.charAt(i);
+  else if(cc<=0x7ff)
+   rv += String.fromCharCode(
+    0xc0|((cc>> 6)&0x1f),
+    0x80|( cc     &0x3f) );
+  else if(cc<=0xffff)
+   rv += String.fromCharCode(
+    0xe0|((cc>>12)&0x0f),
+    0x80|((cc>> 6)&0x3f),
+    0x80|( cc     &0x3f) );
+  else if(cc<=0x1fffff)
+   rv += String.fromCharCode(
+    0xf0|((cc>>18)&0x07),
+    0x80|((cc>>12)&0x3f),
+    0x80|((cc>> 6)&0x3f),
+    0x80|( cc     &0x3f) );
+  else if(cc<=0x03ffffff)
+   rv += String.fromCharCode(
+    0xf8|((cc>>24)&0x03),
+    0x80|((cc>>18)&0x3f),
+    0x80|((cc>>12)&0x3f),
+    0x80|((cc>> 6)&0x3f),
+    0x80|( cc     &0x3f) );
+  else if(cc<=0x7fffffff)
+   rv += String.fromCharCode(
+    0xfc|((cc>>30)&0x01),
+    0x80|((cc>>24)&0x3f),
+    0x80|((cc>>18)&0x3f),
+    0x80|((cc>>12)&0x3f),
+    0x80|((cc>> 6)&0x3f),
+    0x80|( cc     &0x3f) );
+ }
+ return rv;
+}
+function xp_str(xp,x) {
+ var rv = x.evaluate(
+  xp, x, null, XPathResult.STRING_TYPE, null );
+ return rv.stringValue;
+}
+function xp_node(xp,x) {
+ var rv = x.evaluate(
+  xp, x, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null );
+ return rv.singleNodeValue;
+}
+
+function Flickr() { }
+Flickr.prototype = {
+
+ rest_url: 'http://www.flickr.com/services/rest/',
+ auth_url: 'http://flickr.com/services/auth/',
+ photo_url: 'http://static.flickr.com/',
+ photos_url: 'http://www.flickr.com/photos/',
+ upload_url: 'http://www.flickr.com/services/upload/',
+
+ api_sig: function(paramstr) {
+  return MD5(toutf8(this.api_shs+paramstr));
+ },
+ api_call_url: function(params,url) {
+  params.api_key = this.api_key;
+  var pp = new Array();
+  for(var p in params) {
+   pp.push(p);
+  }
+  var pstr = '';
+  var rv = (url?url:this.rest_url)+'?';
+  for(var p in pp.sort()) {
+   var pn = pp[p];
+   pstr += pn+params[pn];
+   rv += pn+'='+params[pn]+'&';
+  }
+  rv += 'api_sig='+this.api_sig(pstr);
+  return rv;
+ },
+ api_call: function(params, on_success, on_failure) {
+  if(params.auth_token == 'default')
+   params.auth_token = this.token;
+  var x = new XMLHttpRequest();
+  x.open("GET",this.api_call_url(params));
+  x.onreadystatechange=function() {
+   if(x.readyState!=4) return false;
+   if(x.status==200) {
+    var stat = x.responseXML.firstChild.getAttribute('stat');
+    if(stat=='ok') {
+     if(on_success) on_success(x);
+    }else{
+     var e = x.responseXML.getElementsByTagName('err').item(0);
+     var ecode = e.getAttribute('code');
+     var emsg = e.getAttribute('msg');
+     dump(params.method+' failed: '+ecode+' '+emsg+'\n');
+     if(on_failure) on_failure(x,stat,ecode,emsg);
+    }
+   }else{
+    if(on_failure) on_failure(x);
+   }
+   return true;
+  }
+  x.send(null);
+  return true;
+ },
+
+ frob: null,
+ authorize_0: function(on_s, on_f) {
+  var _this = this;
+  this.api_call(
+   { method: 'flickr.auth.getFrob' },
+   function(x) {
+    _this.frob = xp_str('/rsp/frob',x.responseXML);
+    var u = _this.api_call_url(
+     { frob: _this.frob, perms: 'delete' }, _this.auth_url );
+    var wm = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(
+     Components.interfaces.nsIWindowMediator );
+    var bw = wm.getMostRecentWindow('navigator:browser');
+    var b = bw.getBrowser();
+    var t = b.addTab(u);
+    b.selectedTab = t;
+    if(on_s) on_s();
+   }, function(x,s,c,m) {
+    if(on_f) on_f(x,s,c,m);
+   }
+  );
+ },
+ token: null,
+ perms: null,
+ user: null,
+ authorize_1: function(on_s, on_f) {
+  var _this = this;
+  this.api_call(
+   { method: 'flickr.auth.getToken', frob: this.frob },
+   function(x) {
+    _this.token = xp_str('/rsp/auth/token',x.responseXML);
+    _this.perms = xp_str('/rsp/auth/perms',x.responseXML);
+    var u = xp_node('/rsp/auth/user',x.responseXML);
+    _this.user = {
+     nsid: u.getAttribute('nsid'),
+     username: u.getAttribute('username'),
+     fullname: u.getAttribute('fullname')
+    };
+    if(on_s) on_s(x);
+   }, function(x,s,c,m) {
+    if(on_f) on_f(x,s,c,m);
+   }
+  );
+ },
+
+ prefs: Components.classes['@mozilla.org/preferences-service;1'].getService(
+  Components.interfaces.nsIPrefBranch
+ ),
+ prefs_root: 'net.klever.kin.flickr',
+ save_token: function() {
+  // TODO: don't clear when there's nothing to clear or catch exceptions
+  if(this.token)
+   this.prefs.setCharPref(this.prefs_root+'.auth_token',this.token);
+  else
+   this.prefs.clearUserPref(this.prefs_root+'.auth_token');
+  if(this.perms)
+   this.prefs.setCharPref(this.prefs_root+'.auth_perms',this.perms);
+  else
+   this.prefs.clearUserPref(this.prefs_root+'.auth_perms');
+  if(this.user && this.user.nsid!=null && this.user.nsid!=undefined)
+   this.prefs.setCharPref(this.prefs_root+'.auth_user.nsid',this.user.nsid);
+  else
+   this.prefs.clearUserPref(this.prefs_root+'.auth_user.nsid');
+  if(this.user && this.user.username!=null && this.user.username!=undefined)
+   this.prefs.setCharPref(this.prefs_root+'.auth_user.username',this.user.username);
+  else
+   this.prefs.clearUserPref(this.prefs_root+'.auth_user.username');
+  if(this.user && this.user.fullname!=null && this.user.fullname!=undefined)
+   this.prefs.setCharPref(this.prefs_root+'.auth_user.fullname',this.user.fullname);
+  else
+   this.prefs.clearUserPref(this.prefs_root+'.auth_user.fullname');
+ },
+ _reset_token: function() {
+  this.token = null; this.perms = null; this.user = null;
+  return false;
+ },
+ load_token: function() {
+  try {
+   if(this.prefs.getPrefType(this.prefs_root+'.auth_token')!=this.prefs.PREF_STRING)
+    return this._reset_token();
+   this.token = this.prefs.getCharPref(this.prefs_root+'.auth_token');
+   if(this.prefs.getPrefType(this.prefs_root+'.auth_perms')!=this.prefs.PREF_STRING)
+    return this._reset_token();
+   this.perms = this.prefs.getCharPref(this.prefs_root+'.auth_perms');
+   if(this.prefs.getPrefType(this.prefs_root+'.auth_user.nsid')!=this.prefs.PREF_STRING)
+    return this._reset_token();
+   this.user = new Object();
+   this.user.nsid = this.prefs.getCharPref(this.prefs_root+'.auth_user.nsid');
+   if(this.prefs.getPrefType(this.prefs_root+'.auth_user.username')!=this.prefs.PREF_STRING)
+    return this._reset_token();
+   this.user.username = this.prefs.getCharPref(this.prefs_root+'.auth_user.username');
+   if(this.prefs.getPrefType(this.prefs_root+'.auth_user.fullname')!=this.prefs.PREF_STRING)
+    return this._reset_token();
+   this.user.fullname = this.prefs.getCharPref(this.prefs_root+'.auth_user.fullname');
+  }catch(e) { return this._reset_token(); }
+  return true;
+ },
+ reset_token: function() {
+  this._reset_token();
+  this.save_token();
+ },
+
+ get_photo_url: function(ser,id,sec,sfx,ext) {
+  var rv = this.photo_url + ser + '/' + id + '_' + sec;
+  if(sfx && sfx!='_') rv += '_'+sfx;
+  rv += ext?'.'+ext:'.jpg';
+  return rv;
+ },
+ get_image_url: function(o,sfx) {
+  return this.get_photo_url(
+   o.server,
+   (o instanceof Photoset)? o.primary : o.id,
+   o.secret,
+   sfx,
+   (sfx=='o')?o.originalformat:null
+  );
+ },
+ get_photo_page_url: function(p) {
+  if(p instanceof Photo) // TODO: half wrong, what if no owner?
+   return this.photos_url + (p.owner.nsid?p.owner.nsid:this.user.nsid) + '/' + p.id;
+  else // TODO: take owner into account?
+   return this.photos_url + this.user.nsid + '/' + p;
+ },
+ make_photo_url: function(p,sfx) {
+  if(sfx=='p')
+   return this.get_photo_page_url(p);
+  else
+   return this.get_image_url(p,sfx);
+ },
+
+ upload_file: function(f,fa,on_success,on_failure) {
+  try {
+   var fi = Components.classes["@mozilla.org/file/local;1"]
+    .createInstance(Components.interfaces.nsILocalFile);
+   fi.initWithPath( f );
+   var st = Components.classes["@mozilla.org/network/file-input-stream;1"]
+    .createInstance(Components.interfaces.nsIFileInputStream);
+   st.init(fi,0x01,00004,null);
+   var bis = Components.classes["@mozilla.org/binaryinputstream;1"]
+    .createInstance(Components.interfaces.nsIBinaryInputStream);
+   bis.setInputStream(st);
+
+   // allocate and initialize temp storage string
+   var pbs = Components.classes["@mozilla.org/storagestream;1"]
+    .createInstance(Components.interfaces.nsIStorageStream);
+   pbs.init(1024,10000000,null);
+   // create output stream
+   var pbos = pbs.getOutputStream(0);
+   // and a binaryoutputstream interface
+   var pbbos = Components.classes["@mozilla.org/binaryoutputstream;1"]
+    .createInstance(Components.interfaces.nsIBinaryOutputStream);
+   pbbos.setOutputStream(pbos);
+
+   /* create POST body */
+   var boundarytoken = 'kadaroloongazaduviaxamma';
+   var boundary = '--'+boundarytoken;
+   var b = '';
+
+   var parms = { api_key: this.api_key, auth_token: this.token };
+   for(var p in fa) parms[p] = fa[p];
+   var pns = new Array();
+   for(var p in parms) pns.push(p);
+   var pstr = '';
+   for(var p in pns.sort()) {
+    var pn = pns[p];
+    pstr += pn+parms[pn];
+    b += boundary+'\nContent-Disposition: form-data; name="'+pn+'"\n\n'+toutf8(parms[pn])+'\n';
+   }
+   b += boundary+'\nContent-Disposition: form-data; name="api_sig"\n\n'+this.api_sig(pstr)+'\n';
+   b += boundary+'\nContent-Disposition: form-data; name="photo"; filename="'+f+'"\nContent-Type: image/jpeg\nContent-Transfer-Encoding: binary\n\n';
+   pbbos.writeBytes(b,b.length);
+   var bisbytes = bis.available();
+   pbbos.writeBytes(bis.readBytes(bisbytes),bisbytes);
+   pbbos.writeBytes('\n'+boundary+'--',3+boundary.length); bis.close(); st.close();
+
+   pbbos.close(); pbos.close();
+
+   var x = new XMLHttpRequest();
+   x.open("POST",this.upload_url);
+   x.setRequestHeader('Content-Type', 'multipart/form-data; boundary="'+boundarytoken+'"');
+   x.setRequestHeader('Connection','close');
+   x.setRequestHeader('Content-Length',b.length);
+   x.onreadystatechange=function() {
+    if(x.readyState!=4) return false;
+    if(x.status==200) {
+     var stat = x.responseXML.firstChild.getAttribute('stat');
+     if(stat=='ok') {
+      var pid = xp_str('/rsp/photoid',x.responseXML);
+      if(on_success) on_success(x,pid);
+     }else{
+      var e = x.responseXML.getElementsByTagName('err').item(0);
+      var ecode = e.getAttribute('code');
+      var emsg = e.getAttribute('msg');
+      dump('upload failed: '+ecode+' '+emsg+'\n');
+      if(on_failure) on_failure(x,stat,ecode,emsg);
+     }
+    }else{
+     if(on_failure) on_failure(x);
+    }
+    return true;
+   };
+   x.send(pbs.newInputStream(0));
+  }catch(e) {
+   if(on_failure) on_failure(e,null,-1,e.message);
+  }
+ }
+
+};
diff --git a/content/generated-content.js b/content/generated-content.js
new file mode 100644
index 0000000..0ad08bb
--- a/dev/null
+++ b/content/generated-content.js
@@ -0,0 +1,17 @@
+var generated = {
+ fireflix: null,
+ data: null,
+
+ init: function() {
+  this.fireflix = window.arguments[0];
+  this.data = window.arguments[1];
+  this.databox = document.getElementById('data');
+  this.databox.value = this.data;
+  this.databox.select();
+ },
+ copy: function() {
+  var ch = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+   .getService(Components.interfaces.nsIClipboardHelper);
+  ch.copyString(this.data); 
+ }
+};
diff --git a/content/generated-content.xul b/content/generated-content.xul
new file mode 100644
index 0000000..2a91efa
--- a/dev/null
+++ b/content/generated-content.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xsml-stylesheet href="fireflix.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://fireflix/locale/fireflix.dtd">
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="generated_content"
+ buttons="accept"
+ defaultbutton="accept"
+ title="&generated.title;"
+ onload="generated.init()"
+ >
+
+ <script src="generated-content.js" type="application/x-javascript"/>
+
+ <vbox class="generated wholething" flex="1">
+  <textbox flex="1" minheight="300" minwidth="300" id="data" multiline="true" readonly="true" />
+  <button id="copy" label="&generated.copy;" oncommand="generated.copy()" />
+ </vbox>
+</dialog>
diff --git a/content/icons/16x16/fireflix.png b/content/icons/16x16/fireflix.png
new file mode 100644
index 0000000..377ab28
--- a/dev/null
+++ b/content/icons/16x16/fireflix.png
Binary files differ
diff --git a/content/icons/32x32/fireflix.png b/content/icons/32x32/fireflix.png
new file mode 100644
index 0000000..374ebbd
--- a/dev/null
+++ b/content/icons/32x32/fireflix.png
Binary files differ
diff --git a/content/md5.js b/content/md5.js
new file mode 100644
index 0000000..f22db2b
--- a/dev/null
+++ b/content/md5.js
@@ -0,0 +1,154 @@
+/* MD5 Message-Digest Algorithm - JavaScript
+' MODIFICATION HISTORY:
+' 1.0    16-Feb-2001 - Phil Fresle (sales@frez.co.uk) - Initial Version (VB/ASP code)
+' 1.0    21-Feb-2001 - Enrico Mosanghini (erik504@yahoo.com) - JavaScript porting
+*/
+function MD5(sMessage) {
+ function RotateLeft(lValue, iShiftBits) { return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits)); }
+ function AddUnsigned(lX,lY) {
+	var lX4,lY4,lX8,lY8,lResult;
+	lX8 = (lX & 0x80000000);
+	lY8 = (lY & 0x80000000);
+	lX4 = (lX & 0x40000000);
+	lY4 = (lY & 0x40000000);
+	lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
+	if (lX4 & lY4) return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
+	if (lX4 | lY4) {
+		if (lResult & 0x40000000) return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
+		else return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
+	} else return (lResult ^ lX8 ^ lY8);
+ }
+ function F(x,y,z) { return (x & y) | ((~x) & z); }
+ function G(x,y,z) { return (x & z) | (y & (~z)); }
+ function H(x,y,z) { return (x ^ y ^ z); }
+ function I(x,y,z) { return (y ^ (x | (~z))); }
+ function FF(a,b,c,d,x,s,ac) {
+	a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
+	return AddUnsigned(RotateLeft(a, s), b);
+ }
+ function GG(a,b,c,d,x,s,ac) {
+	a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
+	return AddUnsigned(RotateLeft(a, s), b);
+ }
+ function HH(a,b,c,d,x,s,ac) {
+	a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
+	return AddUnsigned(RotateLeft(a, s), b);
+ }
+ function II(a,b,c,d,x,s,ac) {
+	a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
+	return AddUnsigned(RotateLeft(a, s), b);
+ }
+ function ConvertToWordArray(sMessage) {
+	var lWordCount;
+	var lMessageLength = sMessage.length;
+	var lNumberOfWords_temp1=lMessageLength + 8;
+	var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
+	var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
+	var lWordArray=Array(lNumberOfWords-1);
+	var lBytePosition = 0;
+	var lByteCount = 0;
+	while ( lByteCount < lMessageLength ) {
+		lWordCount = (lByteCount-(lByteCount % 4))/4;
+		lBytePosition = (lByteCount % 4)*8;
+		lWordArray[lWordCount] = (lWordArray[lWordCount] | (sMessage.charCodeAt(lByteCount)<<lBytePosition));
+		lByteCount++;
+	}
+	lWordCount = (lByteCount-(lByteCount % 4))/4;
+	lBytePosition = (lByteCount % 4)*8;
+	lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
+	lWordArray[lNumberOfWords-2] = lMessageLength<<3;
+	lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
+	return lWordArray;
+ }
+ function WordToHex(lValue) {
+	var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
+	for (lCount = 0;lCount<=3;lCount++) {
+		lByte = (lValue>>>(lCount*8)) & 255;
+		WordToHexValue_temp = "0" + lByte.toString(16);
+		WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
+	}
+	return WordToHexValue;
+ }
+	var x=Array();
+	var k,AA,BB,CC,DD,a,b,c,d
+	var S11=7, S12=12, S13=17, S14=22;
+	var S21=5, S22=9 , S23=14, S24=20;
+	var S31=4, S32=11, S33=16, S34=23;
+	var S41=6, S42=10, S43=15, S44=21;
+	// Steps 1 and 2.  Append padding bits and length and convert to words
+	x = ConvertToWordArray(sMessage);
+	// Step 3.  Initialise
+	a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
+	// Step 4.  Process the message in 16-word blocks
+	for (k=0;k<x.length;k+=16) {
+		AA=a; BB=b; CC=c; DD=d;
+		a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
+		d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
+		c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
+		b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
+		a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
+		d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
+		c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
+		b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
+		a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
+		d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
+		c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
+		b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
+		a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
+		d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
+		c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
+		b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
+		a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
+		d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
+		c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
+		b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
+		a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
+		d=GG(d,a,b,c,x[k+10],S22,0x2441453);
+		c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
+		b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
+		a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
+		d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
+		c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
+		b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
+		a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
+		d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
+		c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
+		b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
+		a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
+		d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
+		c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
+		b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
+		a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
+		d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
+		c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
+		b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
+		a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
+		d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
+		c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
+		b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
+		a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
+		d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
+		c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
+		b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
+		a=II(a,b,c,d,x[k+0], S41,0xF4292244);
+		d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
+		c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
+		b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
+		a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
+		d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
+		c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
+		b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
+		a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
+		d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
+		c=II(c,d,a,b,x[k+6], S43,0xA3014314);
+		b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
+		a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
+		d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
+		c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
+		b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
+		a=AddUnsigned(a,AA); b=AddUnsigned(b,BB); c=AddUnsigned(c,CC); d=AddUnsigned(d,DD);
+	}
+	// Step 5.  Output the 128 bit digest
+	var temp= WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);
+	return temp.toLowerCase();
+}
diff --git a/content/photoset-props.js b/content/photoset-props.js
new file mode 100644
index 0000000..43dc1b9
--- a/dev/null
+++ b/content/photoset-props.js
@@ -0,0 +1,81 @@
+
+var psetprops = {
+ fireflix: null,
+ photoset: null,
+ pripic: null,
+
+ settitle: null, setdesc: null,
+ primarypic: null,
+ photos: new Array(),
+ init: function() {
+  this.fireflix = window.arguments[0];
+  this.photoset = window.arguments[1];
+  this.settitle = document.getElementById('set_title');
+  this.settitle.value = this.photoset.title;
+  this.setdesc = document.getElementById('set_desc');
+  this.setdesc.value = this.photoset.description;
+  this.primarypic = document.getElementById('primary_picture');
+  this.primarypic.src =
+   this.fireflix.flickr.get_image_url( this.photoset, 't' );
+  this.primarypic.hidden = false;
+  this.picslist = document.getElementById('primary_picture_list');
+
+  var _this = this;
+  this.fireflix.flickr.api_call(
+   {
+    method: 'flickr.photosets.getPhotos',
+    auth_token: 'default',
+    photoset_id: this.photoset.id
+   }, function(xr) {
+    var x = xr.responseXML;
+    var xp = x.evaluate(
+     '/rsp/photoset/photo', x, null,
+     XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
+    _this.picslist.removeAllItems(); _this.photos= new Array();
+    var n; while(n=xp.iterateNext()) {
+     _this.photos.push(
+      {
+       id: n.getAttribute('id'),
+       secret: n.getAttribute('secret'),
+       server: n.getAttribute('server')
+      }
+     );
+     var ni = _this.picslist.appendItem(
+      n.getAttribute('title'), _this.photos.length-1
+     );
+     ni.setAttribute('command','cmd_select_picture');
+     if(n.getAttribute('isprimary')==1) {
+      _this.picslist.selectedItem = ni;
+      _this.pripic = _this.photos[_this.photos.length-1];
+     }
+    }
+    _this.picslist.hidden = false;
+   }, function() { }
+  );
+ },
+ on_select_picture: function(ev) {
+  var epic = ev.explicitOriginalTarget;
+  this.picslist.selectedItem = epic;
+  var pic = this.photos[this.picslist.selectedItem.value];
+  this.pripic = pic;
+  this.primarypic.src =
+   this.fireflix.flickr.get_photo_url(
+    pic.server,
+    pic.id,
+    pic.secret,
+    't'
+   );
+ },
+ on_accept: function() {
+  this.photoset.title =
+   document.getElementById('set_title').value;
+  this.photoset.description = 
+   document.getElementById('set_desc').value;
+  this.photoset.server = this.pripic.server;
+  this.photoset.primary = this.pripic.id;
+  this.photoset.secret = this.pripic.secret;
+  this.photoset.dirty = true;
+  return;
+ }
+};
+
diff --git a/content/photoset-props.xul b/content/photoset-props.xul
new file mode 100644
index 0000000..e8f6d13
--- a/dev/null
+++ b/content/photoset-props.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xsml-stylesheet href="fireflix.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://fireflix/locale/fireflix.dtd">
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="photoset_props"
+ buttons="accept,cancel"
+ defaultbutton="accept"
+ title="&photosetprops.title;"
+ onload="psetprops.init()"
+ ondialogaccept="psetprops.on_accept()"
+ >
+
+ <script src="photoset-props.js" type="application/x-javascript"/>
+
+ <commandset>
+  <command id="cmd_select_picture"
+  oncommand="psetprops.on_select_picture(event)"/>
+ </commandset>
+ 
+ <hbox class="wholething">
+  <vbox>
+   <menulist id="primary_picture_list" hidden="true" sizetopopup="always"/>
+   <hbox pack="center">
+    <box width="100" pack="center">
+     <image id="primary_picture" hidden="true"/>
+    </box>
+   </hbox>
+  </vbox>
+  <vbox flex="1" minwidth="300">
+   <label control="set_title" value="&photosetprops.set_title.label;"/>
+   <textbox id="set_title" />
+   <label control="set_desc" value="&photosetprops.set_desc.label;"/>
+   <textbox id="set_desc" multiline="true" rows="5" />
+  </vbox>
+ </hbox>
+</dialog>
diff --git a/install.rdf.in b/install.rdf.in
new file mode 100644
index 0000000..73c9d37
--- a/dev/null
+++ b/install.rdf.in
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+  
+  <em:id>@MOZ_EXT_ID@</em:id>
+  <em:name>Fireflix</em:name>
+  <em:version>@VERSION@</em:version>
+  <em:description>Flickr management tool</em:description>
+  <em:creator>Klever Group; http://www.klever.net/</em:creator>
+  <em:homepageURL>http://kin.klever.net/</em:homepageURL>
+  <em:iconURL>chrome://fireflix/content/icons/32x32/fireflix.png</em:iconURL>
+  <em:updateURL>http://kin.klever.net/fireflix/update?v=@VERSION@</em:updateURL>
+  <em:aboutURL>chrome://fireflix/content/about.xul</em:aboutURL>
+
+  <!-- Firefox -->
+  <em:targetApplication>
+   <Description>
+    <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+    <em:minVersion>1.5</em:minVersion>
+    <em:maxVersion>1.5.0.7</em:maxVersion>
+   </Description>
+  </em:targetApplication>
+
+ </Description>
+
+</RDF>
diff --git a/locale/Makefile.am b/locale/Makefile.am
new file mode 100644
index 0000000..0e62920
--- a/dev/null
+++ b/locale/Makefile.am
@@ -0,0 +1,10 @@
+LOCALES=en-US
+nobase_xpichromelocale_DATA = \
+	$(addsuffix /fireflix.dtd, \
+	 ${LOCALES} \
+	) \
+	$(addsuffix /fireflix.properties, \
+	 ${LOCALES} \
+	)
+
+EXTRA_DIST = ${nobase_xpichromelocale_DATA}
diff --git a/locale/en-US/fireflix.dtd b/locale/en-US/fireflix.dtd
new file mode 100644
index 0000000..2b6e72a
--- a/dev/null
+++ b/locale/en-US/fireflix.dtd
@@ -0,0 +1,79 @@
+<!ENTITY % autoconf SYSTEM "chrome://fireflix/content/autoconf.dtd">
+%autoconf;
+
+<!-- About Box -->
+
+<!ENTITY aboutFireflix "About Fireflix" >
+<!ENTITY about.ok.label "OK">
+<!ENTITY about.license.label "License">
+<!ENTITY about.license.tip "Show copying policy">
+
+<!-- COPYING -->
+
+<!ENTITY copying.title "Filreflix: copying policy">
+
+<!-- Sidebar -->
+
+<!ENTITY panel.auth_info "Authorization info">
+<!ENTITY panel.no_auth_info "No auth info available">
+<!ENTITY panel.auth_button "Authorize">
+<!ENTITY panel.auth_complete_button "Authorization complete">
+<!ENTITY panel.flickr_button.label "Flickr">
+<!ENTITY panel.flickr_button.tip "Open Flickr in new tab">
+
+<!ENTITY panel.tabs.search "Search" >
+<!ENTITY panel.tabs.sets "Sets" >
+<!ENTITY panel.tabs.tags "Tags" >
+<!ENTITY panel.tabs.upload "Upload" >
+
+<!ENTITY panel.search.cmd_search.label "Search" >
+<!ENTITY panel.search.search_for.label "Search for:" >
+<!ENTITY panel.search.mode.tagsonly.label "tags">
+<!ENTITY panel.search.mode.tagsonly.tip "Search tags only">
+<!ENTITY panel.search.mode.mine.label "mine">
+<!ENTITY panel.search.col.title.label "Title">
+<!ENTITY panel.search.cmd_search_open.label "Open">
+
+<!ENTITY panel.sets.name.label "Set">
+<!ENTITY panel.sets.name.tip "Photoset name">
+<!ENTITY panel.sets.photos.label "Photos">
+<!ENTITY panel.sets.photos.tip "Number of photos in set">
+
+<!ENTITY panel.sets.cmd_refresh_sets "Refresh">
+<!ENTITY panel.sets.cmd_properties "Properties">
+
+<!ENTITY panel.sets.generate_html "Generate HTML">
+
+<!ENTITY panel.setphotos.title.label "Title">
+<!ENTITY panel.setphotos.title.tip "Picture title">
+<!ENTITY panel.setphotos.taken.label "Taken">
+<!ENTITY panel.setphotos.taken.tip "When the picture was taken">
+<!ENTITY panel.setphotos.upload.label "Uploaded">
+<!ENTITY panel.setphotos.upload.tip "When the picure was uploaded">
+
+<!ENTITY panel.tagslist.tag.label "Tag">
+
+<!ENTITY panel.uploadlist.file.label "File name">
+<!ENTITY panel.uploadlist.title.label "Title">
+<!ENTITY panel.uploadlist.status.label "Status">
+
+<!ENTITY panel.upload_props.filename.label "File:">
+<!ENTITY panel.upload_props.title.label "Title:">
+<!ENTITY panel.upload_props.tags.label "Tags:">
+
+<!ENTITY panel.uploads.upload.label "Upload">
+<!ENTITY panel.uploads.clear.label "Clear">
+<!ENTITY panel.uploads.remove.label "Remove">
+<!ENTITY panel.uploads.add.label "Add">
+<!ENTITY panel.uploads.generate_html "Generate HTML">
+
+<!ENTITY generated.title "Fireflix: Generated content">
+<!ENTITY generated.copy "copy">
+
+<!ENTITY browser.sidebar.label "Fireflix">
+<!ENTITY browser.sidebar.title "Fireflix">
+
+<!ENTITY photosetprops.title "Photoset properties">
+<!ENTITY photosetprops.set_title.label "Photoset title:">
+<!ENTITY photosetprops.set_desc.label "Photoset description:">
+
diff --git a/locale/en-US/fireflix.properties b/locale/en-US/fireflix.properties
new file mode 100644
index 0000000..7caa12f
--- a/dev/null
+++ b/locale/en-US/fireflix.properties
@@ -0,0 +1,11 @@
+postUploadPhotoset=Create a new photoset for uploaded photos (cancel if you don't want to create a photoset)
+
+menutitle_Images=Images
+menutitle_Links=Linked to…
+urltype_s=Small square (75x75)
+urltype_t=Thumbnail (fits in 100x100)
+urltype_m=Small (fits in 240x240)
+urltype__=Medium (fits in 500x500)
+urltype_b=Large (fits in 1024x1024)
+urltype_o=Original image
+urltype_p=Flickr photo URL
diff --git a/update.rdf.in b/update.rdf.in
new file mode 100644
index 0000000..7a78470
--- a/dev/null
+++ b/update.rdf.in
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<RDF:RDF
+  xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+  xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <RDF:Description about="urn:mozilla:extension:@MOZ_EXT_ID@">
+  <em:updates>
+   <RDF:Seq>
+    <RDF:li resource="urn:mozilla:extension:@MOZ_EXT_ID@:@VERSION@"/>
+   </RDF:Seq>
+  </em:updates>
+ </RDF:Description>
+
+ <RDF:Description about="urn:mozilla:extension:@MOZ_EXT_ID@:@VERSION@">
+  <em:version>@VERSION@</em:version>
+  <em:targetApplication>
+   <Description>
+    <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+    <em:minVersion>1.5</em:minVersion>
+    <em:maxVersion>1.5.0.7</em:maxVersion>
+    <em:updateLink>http://kin.klever.net/dist/@PACKAGE@-@VERSION@.xpi</em:updateLink>
+   </Description>
+  </em:targetApplication>
+ </RDF:Description>
+
+</RDF:RDF>
--
cgit v0.9.0.2