summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rwxr-xr-xautobuild.xml52
-rwxr-xr-xdoc/contributions.txt1
-rw-r--r--indra/cmake/LLBase.cmake4
-rw-r--r--indra/llcommon/indra_constants.cpp4
-rw-r--r--indra/llcommon/indra_constants.h4
-rw-r--r--indra/llmessage/llavatarname.cpp23
-rw-r--r--indra/llmessage/llavatarname.h4
-rw-r--r--indra/llprimitive/lldaeloader.cpp6
-rw-r--r--indra/llui/llfloater.cpp16
-rw-r--r--indra/llui/llfloater.h4
-rw-r--r--indra/llui/llfolderview.cpp4
-rw-r--r--indra/llui/llfolderview.h3
-rw-r--r--indra/llui/llfolderviewitem.cpp5
-rw-r--r--indra/llui/lltextbase.cpp26
-rw-r--r--indra/llui/lltextbase.h3
-rw-r--r--indra/llui/lltextutil.cpp20
-rw-r--r--indra/llui/lltextutil.h6
-rw-r--r--indra/llui/llurlaction.cpp10
-rw-r--r--indra/llui/llurlaction.h1
-rw-r--r--indra/newview/CMakeLists.txt60
-rw-r--r--indra/newview/Info-SecondLife.plist2
-rw-r--r--indra/newview/app_settings/commands.xml2
-rw-r--r--indra/newview/app_settings/settings.xml22
-rw-r--r--indra/newview/featuretable.txt2
-rw-r--r--indra/newview/featuretable_linux.txt2
-rw-r--r--indra/newview/featuretable_mac.txt2
-rw-r--r--indra/newview/installers/windows/installer_template.nsi21
-rw-r--r--indra/newview/installers/windows/lang_en-us.nsibin8510 -> 9096 bytes
-rw-r--r--indra/newview/llaisapi.cpp21
-rw-r--r--indra/newview/llappearancemgr.cpp24
-rw-r--r--indra/newview/llappearancemgr.h4
-rw-r--r--indra/newview/llappviewer.cpp6
-rw-r--r--indra/newview/llavataractions.cpp122
-rw-r--r--indra/newview/llavataractions.h5
-rw-r--r--indra/newview/llavatarlist.cpp17
-rw-r--r--indra/newview/llavatarlist.h4
-rw-r--r--indra/newview/llavatarlistitem.cpp22
-rw-r--r--indra/newview/llavatarlistitem.h4
-rw-r--r--indra/newview/llavatarrendernotifier.cpp6
-rw-r--r--indra/newview/llchathistory.cpp21
-rw-r--r--indra/newview/lldrawpoolterrain.cpp17
-rw-r--r--indra/newview/llfloatergesture.cpp3
-rw-r--r--indra/newview/llfloaterimnearbychathandler.cpp25
-rw-r--r--indra/newview/llfloaterimsessiontab.cpp6
-rw-r--r--indra/newview/llfloaterimsessiontab.h2
-rw-r--r--indra/newview/llfloaternamedesc.cpp22
-rw-r--r--indra/newview/llfloaterregioninfo.cpp52
-rw-r--r--indra/newview/llfloaterregioninfo.h7
-rw-r--r--indra/newview/llfloatersnapshot.cpp14
-rw-r--r--indra/newview/llfloatersnapshot.h1
-rw-r--r--indra/newview/llfloaterworldmap.cpp4
-rw-r--r--indra/newview/llfloaterworldmap.h2
-rw-r--r--indra/newview/llinventoryfunctions.cpp6
-rw-r--r--indra/newview/lllocationinputctrl.cpp25
-rw-r--r--indra/newview/lllocationinputctrl.h1
-rw-r--r--indra/newview/lllogchat.cpp3
-rw-r--r--indra/newview/llpanelmaininventory.cpp20
-rw-r--r--indra/newview/llpanelmaininventory.h1
-rw-r--r--indra/newview/llpanelpeople.cpp21
-rw-r--r--indra/newview/llpanelpeoplemenus.cpp88
-rw-r--r--indra/newview/llpanelpeoplemenus.h2
-rw-r--r--indra/newview/llpanelprofile.cpp10
-rw-r--r--indra/newview/llpanelsnapshotinventory.cpp17
-rw-r--r--indra/newview/llpanelwearing.cpp2
-rw-r--r--indra/newview/llpreviewnotecard.cpp6
-rw-r--r--indra/newview/llpreviewtexture.cpp38
-rw-r--r--indra/newview/llstartup.cpp15
-rw-r--r--indra/newview/lltoastnotifypanel.cpp9
-rw-r--r--indra/newview/lltoolmgr.cpp9
-rw-r--r--indra/newview/lltoolmgr.h1
-rw-r--r--indra/newview/lltracker.cpp6
-rw-r--r--indra/newview/lltracker.h2
-rw-r--r--indra/newview/llviewerassetupload.cpp7
-rw-r--r--indra/newview/llviewerpartsource.cpp10
-rw-r--r--indra/newview/llviewerregion.cpp5
-rw-r--r--indra/newview/llviewertexture.cpp3
-rw-r--r--indra/newview/llviewertexturelist.cpp22
-rw-r--r--indra/newview/llvoavatar.cpp72
-rw-r--r--indra/newview/skins/default/xui/en/fonts.xml1
-rw-r--r--indra/newview/skins/default/xui/en/menu_login.xml7
-rw-r--r--indra/newview/skins/default/xui/en/menu_object_icon.xml14
-rw-r--r--indra/newview/skins/default/xui/en/menu_people_friends_view.xml8
-rw-r--r--indra/newview/skins/default/xui/en/menu_people_nearby.xml16
-rw-r--r--indra/newview/skins/default/xui/en/menu_people_nearby_view.xml8
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_objectim.xml7
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml2
-rw-r--r--indra/newview/skins/default/xui/en/menu_wearing_tab.xml7
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml54
-rw-r--r--indra/newview/skins/default/xui/en/panel_login.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_login_first.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_advanced.xml2
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml1
-rwxr-xr-xindra/newview/viewer_manifest.py55
-rw-r--r--indra/viewer_components/Resources/README9
-rw-r--r--indra/viewer_components/Resources/summary.json1
-rw-r--r--indra/viewer_components/manager/InstallerError.py49
-rw-r--r--indra/viewer_components/manager/InstallerUserMessage.py299
-rwxr-xr-xindra/viewer_components/manager/SL_Launcher194
-rwxr-xr-xindra/viewer_components/manager/apply_update.py277
-rwxr-xr-xindra/viewer_components/manager/download_update.py103
-rw-r--r--indra/viewer_components/manager/icons/SL_Logo.gifbin0 -> 1322 bytes
-rw-r--r--indra/viewer_components/manager/icons/SL_Logo.pngbin0 -> 1484 bytes
-rw-r--r--indra/viewer_components/manager/icons/head-sl-logo.gifbin0 -> 960 bytes
-rw-r--r--indra/viewer_components/manager/icons/head-sl-logo.pngbin0 -> 2316 bytes
-rw-r--r--indra/viewer_components/manager/tests/data/settings.xml1184
-rw-r--r--indra/viewer_components/manager/tests/summary.json1
-rw-r--r--indra/viewer_components/manager/tests/test_InstallerError.py39
-rw-r--r--indra/viewer_components/manager/tests/test_check_for_completed_download.py53
-rw-r--r--indra/viewer_components/manager/tests/test_convert_version_file_style.py55
-rw-r--r--indra/viewer_components/manager/tests/test_get_filename.py60
-rw-r--r--indra/viewer_components/manager/tests/test_get_log_file_handle.py63
-rw-r--r--indra/viewer_components/manager/tests/test_get_parent_path.py79
-rw-r--r--indra/viewer_components/manager/tests/test_get_platform_key.py40
-rw-r--r--indra/viewer_components/manager/tests/test_get_settings.py82
-rw-r--r--indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py42
-rw-r--r--indra/viewer_components/manager/tests/test_make_download_dir.py42
-rw-r--r--indra/viewer_components/manager/tests/test_query_vvm.py67
-rw-r--r--indra/viewer_components/manager/tests/test_silent_write.py43
-rw-r--r--indra/viewer_components/manager/tests/test_summary.py39
-rw-r--r--indra/viewer_components/manager/tests/with_setup_args.py66
-rwxr-xr-xindra/viewer_components/manager/update_manager.py512
122 files changed, 4503 insertions, 205 deletions
diff --git a/README.md b/README.md
index ad040fb077..6eddb9aa9e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
Second Life Viewer
====================
-
+
This project manages the source code for the
[Second Life](https://www.secondlife.com) Viewer.
diff --git a/autobuild.xml b/autobuild.xml
index 072dfa678a..469f1c32f5 100755
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -1465,6 +1465,58 @@
<key>version</key>
<string>0.0.1</string>
</map>
+ <key>llbase</key>
+ <map>
+ <key>copyright</key>
+ <string>Copyright (c) 2010, Linden Research, Inc.</string>
+ <key>license</key>
+ <string>mit</string>
+ <key>license_file</key>
+ <string>LICENSES/llbase-license.txt</string>
+ <key>name</key>
+ <string>llbase</string>
+ <key>platforms</key>
+ <map>
+ <key>darwin</key>
+ <map>
+ <key>archive</key>
+ <map>
+ <key>hash</key>
+ <string>13998879705aa1af36c6ea8f480901e3</string>
+ <key>url</key>
+ <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/llbase/rev/318106/arch/Darwin/installer/llbase-0.8.6.318106-darwin-318106.tar.bz2</string>
+ </map>
+ <key>name</key>
+ <string>darwin</string>
+ </map>
+ <key>linux</key>
+ <map>
+ <key>archive</key>
+ <map>
+ <key>hash</key>
+ <string>80ef3f9f6bde28787b02837ace2ad84c</string>
+ <key>url</key>
+ <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/llbase/rev/318106/arch/Linux/installer/llbase-0.8.6.318106-linux-318106.tar.bz2</string>
+ </map>
+ <key>name</key>
+ <string>linux</string>
+ </map>
+ <key>windows</key>
+ <map>
+ <key>archive</key>
+ <map>
+ <key>hash</key>
+ <string>57837daafa60f98a4d9fa4dfe53d995e</string>
+ <key>url</key>
+ <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/llbase/rev/318106/arch/CYGWIN/installer/llbase-0.8.6.0-windows-318106.tar.bz2</string>
+ </map>
+ <key>name</key>
+ <string>windows</string>
+ </map>
+ </map>
+ <key>version</key>
+ <string>0.8.6.318106</string>
+ </map>
<key>llceflib</key>
<map>
<key>copyright</key>
diff --git a/doc/contributions.txt b/doc/contributions.txt
index 9b31e5032a..8fe04e2ff2 100755
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -190,6 +190,7 @@ Ansariel Hiller
STORM-2094
MAINT-5756
MAINT-4677
+ MAINT-6432
Aralara Rajal
Arare Chantilly
CHUIBUG-191
diff --git a/indra/cmake/LLBase.cmake b/indra/cmake/LLBase.cmake
new file mode 100644
index 0000000000..76e3c688a3
--- /dev/null
+++ b/indra/cmake/LLBase.cmake
@@ -0,0 +1,4 @@
+# -*- cmake -*-
+include(Prebuilt)
+
+use_prebuilt_binary(llbase)
diff --git a/indra/llcommon/indra_constants.cpp b/indra/llcommon/indra_constants.cpp
index 60721977cd..7ea42a3fc0 100644
--- a/indra/llcommon/indra_constants.cpp
+++ b/indra/llcommon/indra_constants.cpp
@@ -60,6 +60,10 @@ const LLUUID IMG_SMOKE_POOF ("1e63e323-5fe0-452e-92f8-b98bd0f764e3"); // On d
const LLUUID IMG_BIG_EXPLOSION_1 ("5e47a0dc-97bf-44e0-8b40-de06718cee9d"); // On dataserver
const LLUUID IMG_BIG_EXPLOSION_2 ("9c8eca51-53d5-42a7-bb58-cef070395db8"); // On dataserver
+const LLUUID IMG_ALPHA_GRAD ("e97cf410-8e61-7005-ec06-629eba4cd1fb"); // VIEWER
+const LLUUID IMG_ALPHA_GRAD_2D ("38b86f85-2575-52a9-a531-23108d8da837"); // VIEWER
+const LLUUID IMG_TRANSPARENT ("8dcd4a48-2d37-4909-9f78-f7a9eb4ef903"); // VIEWER
+
const LLUUID IMG_BLOOM1 ("3c59f7fe-9dc8-47f9-8aaf-a9dd1fbc3bef"); // VIEWER
const LLUUID TERRAIN_DIRT_DETAIL ("0bc58228-74a0-7e83-89bc-5c23464bcec5"); // VIEWER
const LLUUID TERRAIN_GRASS_DETAIL ("63338ede-0037-c4fd-855b-015d77112fc8"); // VIEWER
diff --git a/indra/llcommon/indra_constants.h b/indra/llcommon/indra_constants.h
index 02f063f5e8..fda84aa5a8 100644
--- a/indra/llcommon/indra_constants.h
+++ b/indra/llcommon/indra_constants.h
@@ -197,6 +197,10 @@ LL_COMMON_API extern const LLUUID IMG_SMOKE_POOF;
LL_COMMON_API extern const LLUUID IMG_BIG_EXPLOSION_1;
LL_COMMON_API extern const LLUUID IMG_BIG_EXPLOSION_2;
+LL_COMMON_API extern const LLUUID IMG_ALPHA_GRAD;
+LL_COMMON_API extern const LLUUID IMG_ALPHA_GRAD_2D;
+LL_COMMON_API extern const LLUUID IMG_TRANSPARENT;
+
LL_COMMON_API extern const LLUUID IMG_BLOOM1;
LL_COMMON_API extern const LLUUID TERRAIN_DIRT_DETAIL;
LL_COMMON_API extern const LLUUID TERRAIN_GRASS_DETAIL;
diff --git a/indra/llmessage/llavatarname.cpp b/indra/llmessage/llavatarname.cpp
index d12f157910..d2115ee499 100644
--- a/indra/llmessage/llavatarname.cpp
+++ b/indra/llmessage/llavatarname.cpp
@@ -166,7 +166,7 @@ void LLAvatarName::setExpires(F64 expires)
mExpires = LLFrameTimer::getTotalSeconds() + expires;
}
-std::string LLAvatarName::getCompleteName() const
+std::string LLAvatarName::getCompleteName(bool use_parentheses) const
{
std::string name;
if (sUseDisplayNames)
@@ -182,7 +182,14 @@ std::string LLAvatarName::getCompleteName() const
name = mDisplayName;
if(sUseUsernames)
{
- name += " (" + mUsername + ")";
+ if(use_parentheses)
+ {
+ name += " (" + mUsername + ")";
+ }
+ else
+ {
+ name += " [ " + mUsername + " ]";
+ }
}
}
}
@@ -220,7 +227,7 @@ std::string LLAvatarName::getDisplayName() const
}
}
-std::string LLAvatarName::getUserName() const
+std::string LLAvatarName::getUserName(bool lowercase) const
{
std::string name;
if (mLegacyLastName.empty() || (mLegacyLastName == "Resident"))
@@ -238,7 +245,15 @@ std::string LLAvatarName::getUserName() const
}
else
{
- name = mLegacyFirstName + " " + mLegacyLastName;
+ if(lowercase)
+ {
+ name = mLegacyFirstName + "." + mLegacyLastName;
+ LLStringUtil::toLower(name);
+ }
+ else
+ {
+ name = mLegacyFirstName + " " + mLegacyLastName;
+ }
}
return name;
}
diff --git a/indra/llmessage/llavatarname.h b/indra/llmessage/llavatarname.h
index 1cb3ae421f..192f43f07c 100644
--- a/indra/llmessage/llavatarname.h
+++ b/indra/llmessage/llavatarname.h
@@ -65,7 +65,7 @@ public:
// For normal names, returns "James Linden (james.linden)"
// When display names are disabled returns just "James Linden"
- std::string getCompleteName() const;
+ std::string getCompleteName(bool use_parentheses = true) const;
// Returns "James Linden" or "bobsmith123 Resident" for backwards
// compatibility with systems like voice and muting
@@ -80,7 +80,7 @@ public:
// Returns "James Linden" or "bobsmith123 Resident"
// Used where we explicitely prefer or need a non UTF-8 legacy (ASCII) name
// Also used for backwards compatibility with systems like voice and muting
- std::string getUserName() const;
+ std::string getUserName(bool lowercase = false) const;
// Returns "james.linden" or the legacy name for very old names
std::string getAccountName() const { return mUsername; }
diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp
index 720986a411..00bde8dbc3 100644
--- a/indra/llprimitive/lldaeloader.cpp
+++ b/indra/llprimitive/lldaeloader.cpp
@@ -2235,7 +2235,11 @@ std::string LLDAELoader::getElementLabel(daeElement *element)
// retrieve index to distinguish items inside same parent
size_t ind = 0;
parent->getChildren().find(element, ind);
- index_string = "_" + boost::lexical_cast<std::string>(ind);
+
+ if (ind > 0)
+ {
+ index_string = "_" + boost::lexical_cast<std::string>(ind);
+ }
// if parent has a name or ID, use it
std::string name = parent->getAttribute("name");
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index 14f75a2352..5ea9f5b6cc 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -1326,7 +1326,7 @@ void LLFloater::setMinimized(BOOL minimize)
}
mMinimized = FALSE;
-
+ setFrontmost();
// Reshape *after* setting mMinimized
reshape( mExpandedRect.getWidth(), mExpandedRect.getHeight(), TRUE );
}
@@ -1575,6 +1575,7 @@ BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask)
if(offerClickToButton(x, y, mask, BUTTON_TEAR_OFF)) return TRUE;
if(offerClickToButton(x, y, mask, BUTTON_DOCK)) return TRUE;
+ setFrontmost(TRUE, FALSE);
// Otherwise pass to drag handle for movement
return mDragHandle->handleMouseDown(x, y, mask);
}
@@ -1649,7 +1650,7 @@ void LLFloater::setVisibleAndFrontmost(BOOL take_focus,const LLSD& key)
}
}
-void LLFloater::setFrontmost(BOOL take_focus)
+void LLFloater::setFrontmost(BOOL take_focus, BOOL restore)
{
LLMultiFloater* hostp = getHost();
if (hostp)
@@ -1665,7 +1666,7 @@ void LLFloater::setFrontmost(BOOL take_focus)
LLFloaterView * parent = dynamic_cast<LLFloaterView*>( getParent() );
if (parent)
{
- parent->bringToFront(this, take_focus);
+ parent->bringToFront(this, take_focus, restore);
}
// Make sure to set the appropriate transparency type (STORM-732).
@@ -2394,7 +2395,7 @@ LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLF
}
-void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus)
+void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus, BOOL restore)
{
if (!child)
return;
@@ -2478,7 +2479,12 @@ void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus)
{
sendChildToFront(child);
}
- child->setMinimized(FALSE);
+
+ if(restore)
+ {
+ child->setMinimized(FALSE);
+ }
+
if (give_focus && !gFocusMgr.childHasKeyboardFocus(child))
{
child->setFocus(TRUE);
diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h
index ef7c6180d2..165f67499b 100644
--- a/indra/llui/llfloater.h
+++ b/indra/llui/llfloater.h
@@ -310,7 +310,7 @@ public:
/*virtual*/ void setVisible(BOOL visible); // do not override
/*virtual*/ void onVisibilityChange ( BOOL new_visibility ); // do not override
- void setFrontmost(BOOL take_focus = TRUE);
+ void setFrontmost(BOOL take_focus = TRUE, BOOL restore = TRUE);
virtual void setVisibleAndFrontmost(BOOL take_focus=TRUE, const LLSD& key = LLSD());
// Defaults to false.
@@ -547,7 +547,7 @@ public:
void setCycleMode(BOOL mode) { mFocusCycleMode = mode; }
BOOL getCycleMode() const { return mFocusCycleMode; }
- void bringToFront( LLFloater* child, BOOL give_focus = TRUE );
+ void bringToFront( LLFloater* child, BOOL give_focus = TRUE, BOOL restore = TRUE );
void highlightFocusedFloater();
void unhighlightFocusedFloater();
void focusFrontFloater();
diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp
index 3282c5f726..8166ef6a07 100644
--- a/indra/llui/llfolderview.cpp
+++ b/indra/llui/llfolderview.cpp
@@ -1629,9 +1629,9 @@ void LLFolderView::update()
if (mNeedsAutoSelect)
{
LL_RECORD_BLOCK_TIME(FTM_AUTO_SELECT);
- // select new item only if a filtered item not currently selected
+ // select new item only if a filtered item not currently selected and there was a selection
LLFolderViewItem* selected_itemp = mSelectedItems.empty() ? NULL : mSelectedItems.back();
- if (!mAutoSelectOverride && (!selected_itemp || !selected_itemp->getViewModelItem()->potentiallyVisible()))
+ if (!mAutoSelectOverride && selected_itemp && !selected_itemp->getViewModelItem()->potentiallyVisible())
{
// these are named variables to get around gcc not binding non-const references to rvalues
// and functor application is inherently non-const to allow for stateful functors
diff --git a/indra/llui/llfolderview.h b/indra/llui/llfolderview.h
index 114dd7bd2f..b5deefd653 100644
--- a/indra/llui/llfolderview.h
+++ b/indra/llui/llfolderview.h
@@ -242,6 +242,8 @@ public:
bool useLabelSuffix() { return mUseLabelSuffix; }
virtual void updateMenu();
+ void finishRenamingItem( void );
+
// Note: We may eventually have to move that method up the hierarchy to LLFolderViewItem.
LLHandle<LLFolderView> getHandle() const { return getDerivedHandle<LLFolderView>(); }
@@ -255,7 +257,6 @@ protected:
void commitRename( const LLSD& data );
void onRenamerLost();
- void finishRenamingItem( void );
void closeRenamer( void );
bool selectFirstItem();
diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp
index 3def0386e1..5eb5ca4f82 100644
--- a/indra/llui/llfolderviewitem.cpp
+++ b/indra/llui/llfolderviewitem.cpp
@@ -972,6 +972,11 @@ void LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder)
mIndentation = (getParentFolder())
? getParentFolder()->getIndentation() + mLocalIndentation
: 0;
+
+ if(isOpen() && folder->isOpen())
+ {
+ requestArrange();
+ }
}
static LLTrace::BlockTimerStatHandle FTM_ARRANGE("Arrange");
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index 616c42895c..c7d7535f87 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -177,6 +177,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
: LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)),
mURLClickSignal(NULL),
mIsFriendSignal(NULL),
+ mIsObjectBlockedSignal(NULL),
mMaxTextByteLength( p.max_text_length ),
mFont(p.font),
mFontShadow(p.font_shadow),
@@ -268,6 +269,8 @@ LLTextBase::~LLTextBase()
{
mSegments.clear();
delete mURLClickSignal;
+ delete mIsFriendSignal;
+ delete mIsObjectBlockedSignal;
}
void LLTextBase::initFromParams(const LLTextBase::Params& p)
@@ -1942,6 +1945,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url)
registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url));
registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url, true));
registrar.add("Url.Block", boost::bind(&LLUrlAction::blockObject, url));
+ registrar.add("Url.Unblock", boost::bind(&LLUrlAction::unblockObject, url));
registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url));
registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url));
registrar.add("Url.AddFriend", boost::bind(&LLUrlAction::addFriend, url));
@@ -1968,6 +1972,19 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url)
removeFriendButton->setEnabled(isFriend);
}
}
+
+ if (mIsObjectBlockedSignal)
+ {
+ bool is_blocked = *(*mIsObjectBlockedSignal)(LLUUID(LLUrlAction::getObjectId(url)), LLUrlAction::getObjectName(url));
+ LLView* blockButton = mPopupMenu->getChild<LLView>("block_object");
+ LLView* unblockButton = mPopupMenu->getChild<LLView>("unblock_object");
+
+ if (blockButton && unblockButton)
+ {
+ blockButton->setVisible(!is_blocked);
+ unblockButton->setVisible(is_blocked);
+ }
+ }
if (mPopupMenu)
{
@@ -3022,6 +3039,15 @@ boost::signals2::connection LLTextBase::setIsFriendCallback(const is_friend_sign
return mIsFriendSignal->connect(cb);
}
+boost::signals2::connection LLTextBase::setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb)
+{
+ if (!mIsObjectBlockedSignal)
+ {
+ mIsObjectBlockedSignal = new is_blocked_signal_t();
+ }
+ return mIsObjectBlockedSignal->connect(cb);
+}
+
//
// LLTextSegment
//
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index c6ce5efcb8..85641fd899 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -270,6 +270,7 @@ public:
friend class LLUICtrlFactory;
typedef boost::signals2::signal<bool (const LLUUID& user_id)> is_friend_signal_t;
+ typedef boost::signals2::signal<bool (const LLUUID& blocked_id, const std::string from)> is_blocked_signal_t;
struct LineSpacingParams : public LLInitParam::ChoiceBlock<LineSpacingParams>
{
@@ -456,6 +457,7 @@ public:
virtual void appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo);
boost::signals2::connection setURLClickedCallback(const commit_signal_t::slot_type& cb);
boost::signals2::connection setIsFriendCallback(const is_friend_signal_t::slot_type& cb);
+ boost::signals2::connection setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb);
void setWordWrap(bool wrap);
LLScrollContainer* getScrollContainer() const { return mScroller; }
@@ -685,6 +687,7 @@ protected:
// Used to check if user with given ID is avatar's friend
is_friend_signal_t* mIsFriendSignal;
+ is_blocked_signal_t* mIsObjectBlockedSignal;
LLUIString mLabel; // text label that is visible when no user text provided
};
diff --git a/indra/llui/lltextutil.cpp b/indra/llui/lltextutil.cpp
index fff04b34f2..f6b2ee1dc0 100644
--- a/indra/llui/lltextutil.cpp
+++ b/indra/llui/lltextutil.cpp
@@ -56,6 +56,26 @@ void LLTextUtil::textboxSetHighlightedVal(LLTextBox *txtbox, const LLStyle::Para
txtbox->appendText(text.substr(hl_begin + hl_len), false, normal_style);
}
+void LLTextUtil::textboxSetGreyedVal(LLTextBox *txtbox, const LLStyle::Params& normal_style, const std::string& text, const std::string& greyed)
+{
+ static LLUIColor sGreyedTextColor = LLUIColorTable::instance().getColor("Gray", LLColor4::grey);
+
+ size_t greyed_begin = 0, greyed_len = greyed.size();
+
+ if (greyed_len == 0 || (greyed_begin = text.find(greyed)) == std::string::npos)
+ {
+ txtbox->setText(text, normal_style);
+ return;
+ }
+
+ LLStyle::Params greyed_style = normal_style;
+ greyed_style.color = sGreyedTextColor;
+ txtbox->setText(LLStringUtil::null); // clear text
+ txtbox->appendText(text.substr(0, greyed_begin), false, normal_style);
+ txtbox->appendText(text.substr(greyed_begin, greyed_len), false, greyed_style);
+ txtbox->appendText(text.substr(greyed_begin + greyed_len), false, normal_style);
+}
+
const std::string& LLTextUtil::formatPhoneNumber(const std::string& phone_str)
{
static const std::string PHONE_SEPARATOR = LLUI::sSettingGroups["config"]->getString("AvalinePhoneSeparator");
diff --git a/indra/llui/lltextutil.h b/indra/llui/lltextutil.h
index 1be81ffd62..a9c143e445 100644
--- a/indra/llui/lltextutil.h
+++ b/indra/llui/lltextutil.h
@@ -52,6 +52,12 @@ namespace LLTextUtil
const std::string& text,
const std::string& hl);
+ void textboxSetGreyedVal(
+ LLTextBox *txtbox,
+ const LLStyle::Params& normal_style,
+ const std::string& text,
+ const std::string& greyed);
+
/**
* Formats passed phone number to be more human readable.
*
diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp
index 56977c597b..84ea770a8d 100644
--- a/indra/llui/llurlaction.cpp
+++ b/indra/llui/llurlaction.cpp
@@ -231,3 +231,13 @@ void LLUrlAction::blockObject(std::string url)
executeSLURL("secondlife:///app/agent/" + object_id + "/block/" + LLURI::escape(object_name));
}
}
+
+void LLUrlAction::unblockObject(std::string url)
+{
+ std::string object_id = getObjectId(url);
+ std::string object_name = getObjectName(url);
+ if (LLUUID::validate(object_id))
+ {
+ executeSLURL("secondlife:///app/agent/" + object_id + "/unblock/" + object_name);
+ }
+}
diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h
index 5497e28bb4..2d2a8dfef1 100644
--- a/indra/llui/llurlaction.h
+++ b/indra/llui/llurlaction.h
@@ -83,6 +83,7 @@ public:
static void addFriend(std::string url);
static void removeFriend(std::string url);
static void blockObject(std::string url);
+ static void unblockObject(std::string url);
/// specify the callbacks to enable this class's functionality
typedef boost::function<void (const std::string&)> url_callback_t;
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 8d863631cf..31e3711569 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -17,6 +17,7 @@ include(GooglePerfTools)
include(Hunspell)
include(JsonCpp)
include(LLAppearance)
+include(LLBase)
include(LLAudio)
include(LLCharacter)
include(LLCommon)
@@ -1315,13 +1316,10 @@ set(viewer_HEADER_FILES
source_group("CMake Rules" FILES ViewerInstall.cmake)
+#summary.json creation moved to viewer_manifest.py MAINT-6413
# the viewer_version.txt file created here is for passing to viewer_manifest and autobuild
-# the summary.json file is created for the benefit of the TeamCity builds, where
-# it is used to provide descriptive information to the build results page
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt"
"${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}\n")
-file(WRITE "${CMAKE_BINARY_DIR}/summary.json"
- "{\"Type\":\"viewer\",\"Version\":\"${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}\"}\n")
set_source_files_properties(
llversioninfo.cpp tests/llversioninfo_test.cpp
@@ -1764,6 +1762,58 @@ if (WINDOWS)
${SHARED_LIB_STAGING_DIR}/Debug/fmodexL.dll
)
endif (FMODEX)
+
+ get_filename_component(PYTHON_DIRECTORY ${PYTHON_EXECUTABLE} DIRECTORY)
+
+ # http://pythonhosted.org/PyInstaller/#options
+ add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/SL_Launcher.exe
+ COMMAND ${PYTHON_DIRECTORY}/Scripts/pyinstaller.exe
+ ARGS
+ --onefile
+ --log-level WARN
+ --distpath ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}
+ ${CMAKE_SOURCE_DIR}/viewer_components/manager/SL_Launcher
+ COMMENT "Performing pyinstaller compile of SL_Launcher"
+)
+
+ add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/apply_update.exe
+ COMMAND ${PYTHON_DIRECTORY}/Scripts/pyinstaller.exe
+ ARGS
+ --onefile
+ --log-level WARN
+ --distpath ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}
+ ${CMAKE_SOURCE_DIR}/viewer_components/manager/apply_update.py
+ COMMENT "Performing pyinstaller compile of updater"
+)
+
+ add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/download_update.exe
+ COMMAND ${PYTHON_DIRECTORY}/Scripts/pyinstaller.exe
+ ARGS
+ --onefile
+ --log-level WARN
+ --distpath ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}
+ ${CMAKE_SOURCE_DIR}/viewer_components/manager/download_update.py
+ COMMENT "Performing pyinstaller compile of update downloader"
+)
+
+ add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/update_manager.exe
+ COMMAND ${PYTHON_DIRECTORY}/Scripts/pyinstaller.exe
+ ARGS
+ --onefile
+ --log-level WARN
+ --distpath ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}
+ ${CMAKE_SOURCE_DIR}/viewer_components/manager/update_manager.py
+ COMMENT "Performing pyinstaller compile of update manager"
+)
+
+add_custom_target(compile_w_viewer_launcher ALL DEPENDS ${CMAKE_CFG_INTDIR}/SL_Launcher.exe)
+add_custom_target(compile_w_viewer_updater ALL DEPENDS ${CMAKE_CFG_INTDIR}/apply_update.exe)
+add_custom_target(compile_w_viewer_downloader ALL DEPENDS ${CMAKE_CFG_INTDIR}/download_update.exe)
+add_custom_target(compile_w_viewer_update_manager ALL DEPENDS ${CMAKE_CFG_INTDIR}/update_manager.exe)
add_custom_command(
OUTPUT ${CMAKE_CFG_INTDIR}/copy_touched.bat
@@ -2030,6 +2080,8 @@ endif (LINUX)
if (DARWIN)
# These all get set with PROPERTIES
set(product "Second Life")
+ # this is the setting for the Python wrapper, see SL-322 and WRAPPER line in Info-SecondLife.plist
+ set(MACOSX_WRAPPER_EXECUTABLE_NAME "SL_Launcher")
set(MACOSX_BUNDLE_INFO_STRING "Second Life Viewer")
set(MACOSX_BUNDLE_ICON_FILE "secondlife.icns")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.secondlife.indra.viewer")
diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist
index 9b8136a827..8aabd6818b 100644
--- a/indra/newview/Info-SecondLife.plist
+++ b/indra/newview/Info-SecondLife.plist
@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
- <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+ <string>${MACOSX_WRAPPER_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml
index 2cd6638042..a0d3dc0f99 100644
--- a/indra/newview/app_settings/commands.xml
+++ b/indra/newview/app_settings/commands.xml
@@ -37,7 +37,7 @@
tooltip_ref="Command_Build_Tooltip"
execute_function="Build.Toggle"
execute_parameters="build"
- is_enabled_function="Build.Enabled"
+ is_enabled_function="Build.EnabledOrActive"
is_enabled_parameters="build"
is_running_function="Floater.IsOpen"
is_running_parameters="build"
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index a8d42be2a1..4912f27e70 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -11065,6 +11065,28 @@
<key>Value</key>
<integer>1</integer>
</map>
+ <key>FriendsListHideUsernames</key>
+ <map>
+ <key>Comment</key>
+ <string>Show both Display name and Username in Friend list</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>NearbyListHideUsernames</key>
+ <map>
+ <key>Comment</key>
+ <string>Show both Display name and Username in Nearby list</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>NearbyListShowMap</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt
index be8ea2bab9..e99b94f150 100644
--- a/indra/newview/featuretable.txt
+++ b/indra/newview/featuretable.txt
@@ -32,7 +32,7 @@ RenderAvatarCloth 1 1
RenderAvatarLODFactor 1 1.0
RenderAvatarPhysicsLODFactor 1 1.0
RenderAvatarMaxNonImpostors 1 16
-RenderAvatarMaxComplexity 1 80000
+RenderAvatarMaxComplexity 1 350000
RenderAvatarVP 1 1
RenderAutoMuteSurfaceAreaLimit 1 1000.0
RenderCubeMap 1 1
diff --git a/indra/newview/featuretable_linux.txt b/indra/newview/featuretable_linux.txt
index ca6c00951d..801a622e93 100644
--- a/indra/newview/featuretable_linux.txt
+++ b/indra/newview/featuretable_linux.txt
@@ -32,7 +32,7 @@ RenderAvatarCloth 1 1
RenderAvatarLODFactor 1 1.0
RenderAvatarPhysicsLODFactor 1 1.0
RenderAvatarMaxNonImpostors 1 16
-RenderAvatarMaxComplexity 1 80000
+RenderAvatarMaxComplexity 1 350000
RenderAvatarVP 1 1
RenderAutoMuteSurfaceAreaLimit 1 1000.0
RenderCubeMap 1 1
diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt
index ea69b088f9..1f891ee4d7 100644
--- a/indra/newview/featuretable_mac.txt
+++ b/indra/newview/featuretable_mac.txt
@@ -32,7 +32,7 @@ RenderAvatarCloth 1 1
RenderAvatarLODFactor 1 1.0
RenderAvatarPhysicsLODFactor 1 1.0
RenderAvatarMaxNonImpostors 1 16
-RenderAvatarMaxComplexity 1 80000
+RenderAvatarMaxComplexity 1 350000
RenderAvatarVP 1 1
RenderAutoMuteSurfaceAreaLimit 1 1000.0
RenderCubeMap 1 1
diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi
index b8677fd9e4..fee6e451ec 100644
--- a/indra/newview/installers/windows/installer_template.nsi
+++ b/indra/newview/installers/windows/installer_template.nsi
@@ -276,7 +276,7 @@ StrCpy $SHORTCUT_LANG_PARAM "--set InstallLanguage $(LanguageCode)"
CreateDirectory "$SMPROGRAMS\$INSTSHORTCUT"
SetOutPath "$INSTDIR"
CreateShortCut "$SMPROGRAMS\$INSTSHORTCUT\$INSTSHORTCUT.lnk" \
- "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM"
+ "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM" "%%SOURCE%%\icons\release\secondlife.ico"
WriteINIStr "$SMPROGRAMS\$INSTSHORTCUT\SL Create Account.url" \
@@ -294,15 +294,15 @@ CreateShortCut "$SMPROGRAMS\$INSTSHORTCUT\Uninstall $INSTSHORTCUT.lnk" \
# Other shortcuts
SetOutPath "$INSTDIR"
CreateShortCut "$DESKTOP\$INSTSHORTCUT.lnk" \
- "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM"
+ "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM" "%%SOURCE%%\icons\release\secondlife.ico"
CreateShortCut "$INSTDIR\$INSTSHORTCUT.lnk" \
- "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM"
+ "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM" "%%SOURCE%%\icons\release\secondlife.ico"
CreateShortCut "$INSTDIR\Uninstall $INSTSHORTCUT.lnk" \
'"$INSTDIR\uninst.exe"' ''
-# Create *.bat file to specify lang params on first run from installer - see MAINT-5259
+# Create *.bat file to specify lang params on first run from installer - see MAINT-5259S
FileOpen $9 "$INSTDIR\autorun.bat" w
-FileWrite $9 'start "$INSTDIR\$INSTEXE" "$INSTDIR\$INSTEXE" $SHORTCUT_LANG_PARAM$\r$\n'
+FileWrite $9 'start "$INSTDIR\$INSTEXE" /d "$INSTDIR" "$INSTDIR\$INSTEXE" $SHORTCUT_LANG_PARAM$\r$\n'
FileClose $9
# Write registry
@@ -648,6 +648,7 @@ Function un.ProgramFiles
%%DELETE_FILES%%
# Optional/obsolete files. Delete won't fail if they don't exist.
+Delete "$INSTDIR\autorun.bat"
Delete "$INSTDIR\dronesettings.ini"
Delete "$INSTDIR\message_template.msg"
Delete "$INSTDIR\newview.pdb"
@@ -679,6 +680,16 @@ FOLDERFOUND:
NOFOLDER:
+MessageBox MB_YESNO $(DeleteRegistryKeysMB) IDYES DeleteKeys IDNO NoDelete
+
+DeleteKeys:
+ DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Classes\x-grid-location-info"
+ DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Classes\secondlife"
+ DeleteRegKey HKEY_CLASSES_ROOT "x-grid-location-info"
+ DeleteRegKey HKEY_CLASSES_ROOT "secondlife"
+
+NoDelete:
+
FunctionEnd
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/indra/newview/installers/windows/lang_en-us.nsi b/indra/newview/installers/windows/lang_en-us.nsi
index 343c312ddc..aa403a961c 100644
--- a/indra/newview/installers/windows/lang_en-us.nsi
+++ b/indra/newview/installers/windows/lang_en-us.nsi
Binary files differ
diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp
index 3e3d5c7456..648212177b 100644
--- a/indra/newview/llaisapi.cpp
+++ b/indra/newview/llaisapi.cpp
@@ -838,11 +838,11 @@ void AISUpdate::parseEmbeddedCategories(const LLSD& categories)
void AISUpdate::doUpdate()
{
- // Do version/descendent accounting.
+ // Do version/descendant accounting.
for (std::map<LLUUID,S32>::const_iterator catit = mCatDescendentDeltas.begin();
catit != mCatDescendentDeltas.end(); ++catit)
{
- LL_DEBUGS("Inventory") << "descendent accounting for " << catit->first << LL_ENDL;
+ LL_DEBUGS("Inventory") << "descendant accounting for " << catit->first << LL_ENDL;
const LLUUID cat_id(catit->first);
// Don't account for update if we just created this category.
@@ -859,13 +859,13 @@ void AISUpdate::doUpdate()
continue;
}
- // If we have a known descendent count, set that now.
+ // If we have a known descendant count, set that now.
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
if (cat)
{
S32 descendent_delta = catit->second;
S32 old_count = cat->getDescendentCount();
- LL_DEBUGS("Inventory") << "Updating descendent count for "
+ LL_DEBUGS("Inventory") << "Updating descendant count for "
<< cat->getName() << " " << cat_id
<< " with delta " << descendent_delta << " from "
<< old_count << " to " << (old_count+descendent_delta) << LL_ENDL;
@@ -896,7 +896,7 @@ void AISUpdate::doUpdate()
LLUUID category_id(update_it->first);
LLPointer<LLViewerInventoryCategory> new_category = update_it->second;
// Since this is a copy of the category *before* the accounting update, above,
- // we need to transfer back the updated version/descendent count.
+ // we need to transfer back the updated version/descendant count.
LLViewerInventoryCategory* curr_cat = gInventory.getCategory(new_category->getUUID());
if (!curr_cat)
{
@@ -961,7 +961,16 @@ void AISUpdate::doUpdate()
{
LL_WARNS() << "Possible version mismatch for category " << cat->getName()
<< ", viewer version " << cat->getVersion()
- << " server version " << version << LL_ENDL;
+ << " AIS version " << version << " !!!Adjusting local version!!!" << LL_ENDL;
+
+ // the AIS version should be considered the true version. Adjust
+ // our local category model to reflect this version number. Otherwise
+ // it becomes possible to get stuck with the viewer being out of
+ // sync with the inventory system. Under normal circumstances
+ // inventory COF is maintained on the viewer through calls to
+ // LLInventoryModel::accountForUpdate when a changing operation
+ // is performed. This occasionally gets out of sync however.
+ cat->setVersion(version);
}
}
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index cc676550ab..a1d9786321 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -60,6 +60,8 @@
#include "llcoros.h"
#include "lleventcoro.h"
+#include "llavatarpropertiesprocessor.h"
+
#if LL_MSVC
// disable boost::lexical_cast warning
#pragma warning (disable:4702)
@@ -3359,15 +3361,9 @@ void LLAppearanceMgr::requestServerAppearanceUpdate()
{
if (!mOutstandingAppearanceBakeRequest)
{
-#ifdef APPEARANCEBAKE_AS_IN_AIS_QUEUE
mRerequestAppearanceBake = false;
LLCoprocedureManager::CoProcedure_t proc = boost::bind(&LLAppearanceMgr::serverAppearanceUpdateCoro, this, _1);
LLCoprocedureManager::instance().enqueueCoprocedure("AIS", "LLAppearanceMgr::serverAppearanceUpdateCoro", proc);
-#else
- LLCoros::instance().launch("serverAppearanceUpdateCoro",
- boost::bind(&LLAppearanceMgr::serverAppearanceUpdateCoro, this));
-
-#endif
}
else
{
@@ -3375,17 +3371,8 @@ void LLAppearanceMgr::requestServerAppearanceUpdate()
}
}
-#ifdef APPEARANCEBAKE_AS_IN_AIS_QUEUE
void LLAppearanceMgr::serverAppearanceUpdateCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter)
-#else
-void LLAppearanceMgr::serverAppearanceUpdateCoro()
-#endif
{
-#ifndef APPEARANCEBAKE_AS_IN_AIS_QUEUE
- LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(
- new LLCoreHttpUtil::HttpCoroutineAdapter("serverAppearanceUpdateCoro", LLCore::HttpRequest::DEFAULT_POLICY_ID));
-#endif
-
mRerequestAppearanceBake = false;
if (!gAgent.getRegion())
{
@@ -3493,10 +3480,15 @@ void LLAppearanceMgr::serverAppearanceUpdateCoro()
// on multiple machines.
if (result.has("expected"))
{
-
S32 expectedCofVersion = result["expected"].asInteger();
LL_WARNS("Avatar") << "Server expected " << expectedCofVersion << " as COF version" << LL_ENDL;
+ // Force an update texture request for ourself. The message will return
+ // through the UDP and be handled in LLVOAvatar::processAvatarAppearance
+ // this should ensure that we receive a new canonical COF from the sim
+ // host. Hopefully it will return before the timeout.
+ LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(gAgent.getID());
+
bRetry = true;
// Wait for a 1/2 second before trying again. Just to keep from asking too quickly.
if (++retryCount > BAKE_RETRY_MAX_COUNT)
diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h
index bf181cb4ad..7069da7352 100644
--- a/indra/newview/llappearancemgr.h
+++ b/indra/newview/llappearancemgr.h
@@ -228,11 +228,7 @@ public:
private:
-#ifdef APPEARANCEBAKE_AS_IN_AIS_QUEUE
void serverAppearanceUpdateCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter);
-#else
- void serverAppearanceUpdateCoro();
-#endif
static void debugAppearanceUpdateCOF(const LLSD& content);
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index b6d02ea2f8..6bc1f67e32 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -3438,6 +3438,12 @@ std::string LLAppViewer::getViewerInfoString() const
{
support << '\n' << LLTrans::getString("AboutTraffic", args);
}
+
+ // SLT timestamp
+ LLSD substitution;
+ substitution["datetime"] = (S32)time(NULL);//(S32)time_corrected();
+ support << "\n" << LLTrans::getString("AboutTime", substitution);
+
return support.str();
}
diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp
index 00bc8ebe87..a6e745448a 100644
--- a/indra/newview/llavataractions.cpp
+++ b/indra/newview/llavataractions.cpp
@@ -476,13 +476,63 @@ void LLAvatarActions::kick(const LLUUID& id)
}
// static
+void LLAvatarActions::freezeAvatar(const LLUUID& id)
+{
+ std::string fullname;
+ gCacheName->getFullName(id, fullname);
+ LLSD payload;
+ payload["avatar_id"] = id;
+
+ if (!fullname.empty())
+ {
+ LLSD args;
+ args["AVATAR_NAME"] = fullname;
+ LLNotificationsUtil::add("FreezeAvatarFullname", args, payload, handleFreezeAvatar);
+ }
+ else
+ {
+ LLNotificationsUtil::add("FreezeAvatar", LLSD(), payload, handleFreezeAvatar);
+ }
+}
+
+// static
+void LLAvatarActions::ejectAvatar(const LLUUID& id, bool ban_enabled)
+{
+ std::string fullname;
+ gCacheName->getFullName(id, fullname);
+ LLSD payload;
+ payload["avatar_id"] = id;
+ payload["ban_enabled"] = ban_enabled;
+ LLSD args;
+ if (!fullname.empty())
+ {
+ args["AVATAR_NAME"] = fullname;
+ }
+
+ if (ban_enabled)
+ {
+ LLNotificationsUtil::add("EjectAvatarFullname", args, payload, handleEjectAvatar);
+ }
+ else
+ {
+ if (!fullname.empty())
+ {
+ LLNotificationsUtil::add("EjectAvatarFullnameNoBan", args, payload, handleEjectAvatar);
+ }
+ else
+ {
+ LLNotificationsUtil::add("EjectAvatarNoBan", LLSD(), payload, handleEjectAvatar);
+ }
+ }
+}
+
+// static
void LLAvatarActions::freeze(const LLUUID& id)
{
LLSD payload;
payload["avatar_id"] = id;
LLNotifications::instance().add("FreezeUser", LLSD(), payload, handleFreeze);
}
-
// static
void LLAvatarActions::unfreeze(const LLUUID& id)
{
@@ -1133,10 +1183,77 @@ bool LLAvatarActions::handleKick(const LLSD& notification, const LLSD& response)
}
return false;
}
-bool LLAvatarActions::handleFreeze(const LLSD& notification, const LLSD& response)
+
+bool LLAvatarActions::handleFreezeAvatar(const LLSD& notification, const LLSD& response)
{
S32 option = LLNotification::getSelectedOption(notification, response);
+ if (0 == option || 1 == option)
+ {
+ U32 flags = 0x0;
+ if (1 == option)
+ {
+ // unfreeze
+ flags |= 0x1;
+ }
+ LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID();
+ LLMessageSystem* msg = gMessageSystem;
+
+ msg->newMessage("FreezeUser");
+ msg->nextBlock("AgentData");
+ msg->addUUID("AgentID", gAgent.getID());
+ msg->addUUID("SessionID", gAgent.getSessionID());
+ msg->nextBlock("Data");
+ msg->addUUID("TargetID", avatar_id );
+ msg->addU32("Flags", flags );
+ gAgent.sendReliableMessage();
+ }
+ return false;
+}
+
+bool LLAvatarActions::handleEjectAvatar(const LLSD& notification, const LLSD& response)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (2 == option)
+ {
+ return false;
+ }
+ LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID();
+ bool ban_enabled = notification["payload"]["ban_enabled"].asBoolean();
+
+ if (0 == option)
+ {
+ LLMessageSystem* msg = gMessageSystem;
+ U32 flags = 0x0;
+ msg->newMessage("EjectUser");
+ msg->nextBlock("AgentData");
+ msg->addUUID("AgentID", gAgent.getID() );
+ msg->addUUID("SessionID", gAgent.getSessionID() );
+ msg->nextBlock("Data");
+ msg->addUUID("TargetID", avatar_id );
+ msg->addU32("Flags", flags );
+ gAgent.sendReliableMessage();
+ }
+ else if (ban_enabled)
+ {
+ LLMessageSystem* msg = gMessageSystem;
+
+ U32 flags = 0x1;
+ msg->newMessage("EjectUser");
+ msg->nextBlock("AgentData");
+ msg->addUUID("AgentID", gAgent.getID() );
+ msg->addUUID("SessionID", gAgent.getSessionID() );
+ msg->nextBlock("Data");
+ msg->addUUID("TargetID", avatar_id );
+ msg->addU32("Flags", flags );
+ gAgent.sendReliableMessage();
+ }
+ return false;
+}
+
+bool LLAvatarActions::handleFreeze(const LLSD& notification, const LLSD& response)
+{
+ S32 option = LLNotification::getSelectedOption(notification, response);
if (option == 0)
{
LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID();
@@ -1153,6 +1270,7 @@ bool LLAvatarActions::handleFreeze(const LLSD& notification, const LLSD& respons
}
return false;
}
+
bool LLAvatarActions::handleUnfreeze(const LLSD& notification, const LLSD& response)
{
S32 option = LLNotification::getSelectedOption(notification, response);
diff --git a/indra/newview/llavataractions.h b/indra/newview/llavataractions.h
index bd0ac24e93..256d44d820 100644
--- a/indra/newview/llavataractions.h
+++ b/indra/newview/llavataractions.h
@@ -173,6 +173,9 @@ public:
*/
static void inviteToGroup(const LLUUID& id);
+ static void freezeAvatar(const LLUUID& id);
+
+ static void ejectAvatar(const LLUUID& id, bool ban_enabled = false);
/**
* Kick avatar off grid
*/
@@ -242,6 +245,8 @@ private:
static bool callbackAddFriendWithMessage(const LLSD& notification, const LLSD& response);
static bool handleRemove(const LLSD& notification, const LLSD& response);
static bool handlePay(const LLSD& notification, const LLSD& response, LLUUID avatar_id);
+ static bool handleFreezeAvatar(const LLSD& notification, const LLSD& response);
+ static bool handleEjectAvatar(const LLSD& notification, const LLSD& response);
static bool handleKick(const LLSD& notification, const LLSD& response);
static bool handleFreeze(const LLSD& notification, const LLSD& response);
static bool handleUnfreeze(const LLSD& notification, const LLSD& response);
diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp
index 8846d1317d..513f25e301 100644
--- a/indra/newview/llavatarlist.cpp
+++ b/indra/newview/llavatarlist.cpp
@@ -140,6 +140,7 @@ LLAvatarList::LLAvatarList(const Params& p)
, mShowProfileBtn(p.show_profile_btn)
, mShowSpeakingIndicator(p.show_speaking_indicator)
, mShowPermissions(p.show_permissions_granted)
+, mShowCompleteName(false)
{
setCommitOnSelectionChange(true);
@@ -174,6 +175,11 @@ void LLAvatarList::setShowIcons(std::string param_name)
mShowIcons = gSavedSettings.getBOOL(mIconParamName);
}
+std::string LLAvatarList::getAvatarName(LLAvatarName av_name)
+{
+ return mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName();
+}
+
// virtual
void LLAvatarList::draw()
{
@@ -279,7 +285,7 @@ void LLAvatarList::refresh()
LLAvatarName av_name;
have_names &= LLAvatarNameCache::get(buddy_id, &av_name);
- if (!have_filter || findInsensitive(av_name.getDisplayName(), mNameFilter))
+ if (!have_filter || findInsensitive(getAvatarName(av_name), mNameFilter))
{
if (nadded >= ADD_LIMIT)
{
@@ -297,7 +303,7 @@ void LLAvatarList::refresh()
}
else
{
- std::string display_name = av_name.getDisplayName();
+ std::string display_name = getAvatarName(av_name);
addNewItem(buddy_id,
display_name.empty() ? waiting_str : display_name,
LLAvatarTracker::instance().isBuddyOnline(buddy_id));
@@ -327,7 +333,7 @@ void LLAvatarList::refresh()
const LLUUID& buddy_id = it->asUUID();
LLAvatarName av_name;
have_names &= LLAvatarNameCache::get(buddy_id, &av_name);
- if (!findInsensitive(av_name.getDisplayName(), mNameFilter))
+ if (!findInsensitive(getAvatarName(av_name), mNameFilter))
{
removeItemByUUID(buddy_id);
modified = true;
@@ -381,6 +387,7 @@ void LLAvatarList::updateAvatarNames()
for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)
{
LLAvatarListItem* item = static_cast<LLAvatarListItem*>(*it);
+ item->setShowCompleteName(mShowCompleteName);
item->updateAvatarName();
}
mNeedUpdateNames = false;
@@ -400,7 +407,7 @@ bool LLAvatarList::filterHasMatches()
// If name has not been loaded yet we consider it as a match.
// When the name will be loaded the filter will be applied again(in refresh()).
- if (have_name && !findInsensitive(av_name.getDisplayName(), mNameFilter))
+ if (have_name && !findInsensitive(getAvatarName(av_name), mNameFilter))
{
continue;
}
@@ -434,6 +441,7 @@ S32 LLAvatarList::notifyParent(const LLSD& info)
void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, BOOL is_online, EAddPosition pos)
{
LLAvatarListItem* item = new LLAvatarListItem();
+ item->setShowCompleteName(mShowCompleteName);
// This sets the name as a side effect
item->setAvatarId(id, mSessionID, mIgnoreOnlineStatus);
item->setOnline(mIgnoreOnlineStatus ? true : is_online);
@@ -445,6 +453,7 @@ void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, BOOL is
item->showSpeakingIndicator(mShowSpeakingIndicator);
item->setShowPermissions(mShowPermissions);
+
item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoubleClicked, this, _1, _2, _3, _4));
addItem(item, id, pos);
diff --git a/indra/newview/llavatarlist.h b/indra/newview/llavatarlist.h
index 3542577ae3..1a672c279b 100644
--- a/indra/newview/llavatarlist.h
+++ b/indra/newview/llavatarlist.h
@@ -83,6 +83,7 @@ public:
void setShowIcons(std::string param_name);
bool getIconsVisible() const { return mShowIcons; }
const std::string getIconParamName() const{return mIconParamName;}
+ std::string getAvatarName(LLAvatarName av_name);
virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleMouseDown( S32 x, S32 y, MASK mask );
/*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask);
@@ -100,6 +101,8 @@ public:
void addAvalineItem(const LLUUID& item_id, const LLUUID& session_id, const std::string& item_name);
void handleDisplayNamesOptionChanged();
+ void setShowCompleteName(bool show) { mShowCompleteName = show;};
+
protected:
void refresh();
@@ -126,6 +129,7 @@ private:
bool mShowProfileBtn;
bool mShowSpeakingIndicator;
bool mShowPermissions;
+ bool mShowCompleteName;
LLTimer* mLITUpdateTimer; // last interaction time update timer
std::string mIconParamName;
diff --git a/indra/newview/llavatarlistitem.cpp b/indra/newview/llavatarlistitem.cpp
index 3e6c817dd6..af3fac91bc 100644
--- a/indra/newview/llavatarlistitem.cpp
+++ b/indra/newview/llavatarlistitem.cpp
@@ -77,8 +77,10 @@ LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/)
mShowInfoBtn(true),
mShowProfileBtn(true),
mShowPermissions(false),
+ mShowCompleteName(false),
mHovered(false),
- mAvatarNameCacheConnection()
+ mAvatarNameCacheConnection(),
+ mGreyOutUsername("")
{
if (not_from_ui_factory)
{
@@ -399,14 +401,28 @@ void LLAvatarListItem::updateAvatarName()
void LLAvatarListItem::setNameInternal(const std::string& name, const std::string& highlight)
{
- LLTextUtil::textboxSetHighlightedVal(mAvatarName, mAvatarNameStyle, name, highlight);
+ if(mShowCompleteName && highlight.empty())
+ {
+ LLTextUtil::textboxSetGreyedVal(mAvatarName, mAvatarNameStyle, name, mGreyOutUsername);
+ }
+ else
+ {
+ LLTextUtil::textboxSetHighlightedVal(mAvatarName, mAvatarNameStyle, name, highlight);
+ }
}
void LLAvatarListItem::onAvatarNameCache(const LLAvatarName& av_name)
{
mAvatarNameCacheConnection.disconnect();
- setAvatarName(av_name.getDisplayName());
+ mGreyOutUsername = "";
+ std::string name_string = mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName();
+ if(av_name.getCompleteName() != av_name.getUserName())
+ {
+ mGreyOutUsername = "[ " + av_name.getUserName(true) + " ]";
+ LLStringUtil::toLower(mGreyOutUsername);
+ }
+ setAvatarName(name_string);
setAvatarToolTip(av_name.getUserName());
//requesting the list to resort
diff --git a/indra/newview/llavatarlistitem.h b/indra/newview/llavatarlistitem.h
index 7ef35a746e..36d18114aa 100644
--- a/indra/newview/llavatarlistitem.h
+++ b/indra/newview/llavatarlistitem.h
@@ -106,6 +106,7 @@ public:
void setShowPermissions(bool show) { mShowPermissions = show; };
void showLastInteractionTime(bool show);
void setAvatarIconVisible(bool visible);
+ void setShowCompleteName(bool show) { mShowCompleteName = show;};
const LLUUID& getAvatarId() const;
std::string getAvatarName() const;
@@ -218,6 +219,9 @@ private:
/// true when the mouse pointer is hovering over this item
bool mHovered;
+ bool mShowCompleteName;
+ std::string mGreyOutUsername;
+
void fetchAvatarName();
boost::signals2::connection mAvatarNameCacheConnection;
diff --git a/indra/newview/llavatarrendernotifier.cpp b/indra/newview/llavatarrendernotifier.cpp
index a13e142e16..24934fdb73 100644
--- a/indra/newview/llavatarrendernotifier.cpp
+++ b/indra/newview/llavatarrendernotifier.cpp
@@ -63,7 +63,7 @@ mLatestAgentComplexity(0),
mLatestOverLimitPct(0.0f),
mShowOverLimitAgents(false),
mNotifyOutfitLoading(false),
-mLastCofVersion(-1),
+mLastCofVersion(LLViewerInventoryCategory::VERSION_UNKNOWN),
mLastOutfitRezStatus(-1),
mLastSkeletonSerialNum(-1)
{
@@ -207,8 +207,8 @@ void LLAvatarRenderNotifier::updateNotificationState()
mLastSkeletonSerialNum = gAgentAvatarp->mLastSkeletonSerialNum;
}
else if (mLastCofVersion >= 0
- && (mLastCofVersion != gAgentAvatarp->mLastUpdateRequestCOFVersion
- || mLastSkeletonSerialNum != gAgentAvatarp->mLastSkeletonSerialNum))
+ && (mLastCofVersion != LLAppearanceMgr::instance().getCOFVersion()
+ || mLastSkeletonSerialNum != gAgentAvatarp->mLastSkeletonSerialNum))
{
// version mismatch in comparison to previous outfit - outfit changed
mNotifyOutfitLoading = true;
diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp
index 4b426081d0..5d2997688f 100644
--- a/indra/newview/llchathistory.cpp
+++ b/indra/newview/llchathistory.cpp
@@ -156,6 +156,10 @@ public:
LLFloaterSidePanelContainer::showPanel("people", "panel_people",
LLSD().with("people_panel_tab_name", "blocked_panel").with("blocked_to_select", getAvatarId()));
}
+ else if (level == "unblock")
+ {
+ LLMuteList::getInstance()->remove(LLMute(getAvatarId(), mFrom, LLMute::OBJECT));
+ }
else if (level == "map")
{
std::string url = "secondlife://" + mObjectData["slurl"].asString();
@@ -169,6 +173,20 @@ public:
}
+ bool onObjectIconContextMenuItemVisible(const LLSD& userdata)
+ {
+ std::string level = userdata.asString();
+ if (level == "is_blocked")
+ {
+ return LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat);
+ }
+ else if (level == "not_blocked")
+ {
+ return !LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat);
+ }
+ return false;
+ }
+
void onAvatarIconContextMenuItemClicked(const LLSD& userdata)
{
std::string level = userdata.asString();
@@ -275,6 +293,7 @@ public:
registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2));
registrar_enable.add("AvatarIcon.Check", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemChecked, this, _2));
registrar.add("ObjectIcon.Action", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemClicked, this, _2));
+ registrar_enable.add("ObjectIcon.Visible", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemVisible, this, _2));
LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
mPopupMenuHandleAvatar = menu->getHandle();
@@ -719,6 +738,8 @@ LLChatHistory::LLChatHistory(const LLChatHistory::Params& p)
editor_params.trusted_content = false;
mEditor = LLUICtrlFactory::create<LLTextEditor>(editor_params, this);
mEditor->setIsFriendCallback(LLAvatarActions::isFriend);
+ mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0));
+
}
LLSD LLChatHistory::getValue() const
diff --git a/indra/newview/lldrawpoolterrain.cpp b/indra/newview/lldrawpoolterrain.cpp
index 33675bd261..b716a76543 100644
--- a/indra/newview/lldrawpoolterrain.cpp
+++ b/indra/newview/lldrawpoolterrain.cpp
@@ -62,28 +62,15 @@ LLDrawPoolTerrain::LLDrawPoolTerrain(LLViewerTexture *texturep) :
LLFacePool(POOL_TERRAIN),
mTexturep(texturep)
{
- U32 format = GL_ALPHA8;
- U32 int_format = GL_ALPHA;
-
// Hack!
sDetailScale = 1.f/gSavedSettings.getF32("RenderTerrainScale");
sDetailMode = gSavedSettings.getS32("RenderTerrainDetail");
- mAlphaRampImagep = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient.tga",
- FTT_LOCAL_FILE,
- TRUE, LLGLTexture::BOOST_UI,
- LLViewerTexture::FETCHED_TEXTURE,
- format, int_format,
- LLUUID("e97cf410-8e61-7005-ec06-629eba4cd1fb"));
+ mAlphaRampImagep = LLViewerTextureManager::getFetchedTexture(IMG_ALPHA_GRAD);
//gGL.getTexUnit(0)->bind(mAlphaRampImagep.get());
mAlphaRampImagep->setAddressMode(LLTexUnit::TAM_CLAMP);
- m2DAlphaRampImagep = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient_2d.j2c",
- FTT_LOCAL_FILE,
- TRUE, LLGLTexture::BOOST_UI,
- LLViewerTexture::FETCHED_TEXTURE,
- format, int_format,
- LLUUID("38b86f85-2575-52a9-a531-23108d8da837"));
+ m2DAlphaRampImagep = LLViewerTextureManager::getFetchedTexture(IMG_ALPHA_GRAD_2D);
//gGL.getTexUnit(0)->bind(m2DAlphaRampImagep.get());
m2DAlphaRampImagep->setAddressMode(LLTexUnit::TAM_CLAMP);
diff --git a/indra/newview/llfloatergesture.cpp b/indra/newview/llfloatergesture.cpp
index 7da65a9a7c..d842106146 100644
--- a/indra/newview/llfloatergesture.cpp
+++ b/indra/newview/llfloatergesture.cpp
@@ -528,7 +528,8 @@ void LLFloaterGesture::onCopyPasteAction(const LLSD& command)
LLInventoryItem* item = gInventory.getItem(*it);
if(item && item->getInventoryType() == LLInventoryType::IT_GESTURE)
{
- LLClipboard::instance().addToClipboard(item->getUUID(),LLAssetType::AT_GESTURE);
+ LLWString item_name = utf8str_to_wstring(item->getName());
+ LLClipboard::instance().addToClipboard(item_name, 0, item_name.size());
}
}
}
diff --git a/indra/newview/llfloaterimnearbychathandler.cpp b/indra/newview/llfloaterimnearbychathandler.cpp
index 1f85c5ac1b..9fd731ed56 100644
--- a/indra/newview/llfloaterimnearbychathandler.cpp
+++ b/indra/newview/llfloaterimnearbychathandler.cpp
@@ -601,12 +601,31 @@ void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg,
toast_msg = chat_msg.mText;
}
+ bool chat_overlaps = false;
+ if(nearby_chat->getChatHistory())
+ {
+ LLRect chat_rect = nearby_chat->getChatHistory()->calcScreenRect();
+ for (std::list<LLView*>::const_iterator child_iter = gFloaterView->getChildList()->begin();
+ child_iter != gFloaterView->getChildList()->end(); ++child_iter)
+ {
+ LLView *view = *child_iter;
+ const LLRect& rect = view->getRect();
+ if(view->isInVisibleChain() && (rect.overlaps(chat_rect)))
+ {
+ if(!nearby_chat->getChatHistory()->hasAncestor(view))
+ {
+ chat_overlaps = true;
+ }
+ break;
+ }
+ }
+ }
//Don't show nearby toast, if conversation is visible and selected
if ((nearby_chat->hasFocus()) ||
(LLFloater::isVisible(nearby_chat) && nearby_chat->isTornOff() && !nearby_chat->isMinimized()) ||
- ((im_box->getSelectedSession().isNull() &&
- ((LLFloater::isVisible(im_box) && !im_box->isMinimized() && im_box->isFrontmost())
- || (LLFloater::isVisible(nearby_chat) && !nearby_chat->isMinimized() && nearby_chat->isFrontmost())))))
+ ((im_box->getSelectedSession().isNull() && !chat_overlaps &&
+ ((LLFloater::isVisible(im_box) && !nearby_chat->isTornOff() && !im_box->isMinimized())
+ || (LLFloater::isVisible(nearby_chat) && nearby_chat->isTornOff() && !nearby_chat->isMinimized())))))
{
if(nearby_chat->isMessagePaneExpanded())
{
diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp
index 357b635594..2cd94c592a 100644
--- a/indra/newview/llfloaterimsessiontab.cpp
+++ b/indra/newview/llfloaterimsessiontab.cpp
@@ -1094,6 +1094,12 @@ void LLFloaterIMSessionTab::saveCollapsedState()
gSavedPerAccountSettings.setBOOL("NearbyChatIsNotCollapsed", isMessagePaneExpanded());
}
}
+
+LLView* LLFloaterIMSessionTab::getChatHistory()
+{
+ return mChatHistory;
+}
+
BOOL LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask )
{
BOOL handled = FALSE;
diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h
index e7b05a584b..1b4922fd73 100644
--- a/indra/newview/llfloaterimsessiontab.h
+++ b/indra/newview/llfloaterimsessiontab.h
@@ -103,6 +103,8 @@ public:
void restoreFloater();
void saveCollapsedState();
+ LLView* getChatHistory();
+
protected:
// callback for click on any items of the visual states menu
diff --git a/indra/newview/llfloaternamedesc.cpp b/indra/newview/llfloaternamedesc.cpp
index 135bbb335e..4a5732aecf 100644
--- a/indra/newview/llfloaternamedesc.cpp
+++ b/indra/newview/llfloaternamedesc.cpp
@@ -42,6 +42,8 @@
#include "llfloaterperms.h"
#include "llviewercontrol.h"
#include "llviewermenufile.h" // upload_new_resource()
+#include "llstatusbar.h" // can_afford_transaction()
+#include "llnotificationsutil.h"
#include "lluictrlfactory.h"
#include "llstring.h"
#include "lleconomy.h"
@@ -161,12 +163,15 @@ void LLFloaterNameDesc::onBtnOK( )
LLAssetStorage::LLStoreAssetCallback callback = NULL;
S32 expected_upload_cost = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); // kinda hack - assumes that unsubclassed LLFloaterNameDesc is only used for uploading chargeable assets, which it is right now (it's only used unsubclassed for the sound upload dialog, and THAT should be a subclass).
- void *nruserdata = NULL;
- std::string display_name = LLStringUtil::null;
- LLResourceUploadInfo::ptr_t uploadInfo(new LLNewFileResourceUploadInfo(
+ if (can_afford_transaction(expected_upload_cost))
+ {
+ void *nruserdata = NULL;
+ std::string display_name = LLStringUtil::null;
+
+ LLResourceUploadInfo::ptr_t uploadInfo(new LLNewFileResourceUploadInfo(
mFilenameAndPath,
- getChild<LLUICtrl>("name_form")->getValue().asString(),
+ getChild<LLUICtrl>("name_form")->getValue().asString(),
getChild<LLUICtrl>("description_form")->getValue().asString(), 0,
LLFolderType::FT_NONE, LLInventoryType::IT_NONE,
LLFloaterPerms::getNextOwnerPerms("Uploads"),
@@ -174,7 +179,14 @@ void LLFloaterNameDesc::onBtnOK( )
LLFloaterPerms::getEveryonePerms("Uploads"),
expected_upload_cost));
- upload_new_resource(uploadInfo, callback, nruserdata);
+ upload_new_resource(uploadInfo, callback, nruserdata);
+ }
+ else
+ {
+ LLSD args;
+ args["COST"] = llformat("%d", expected_upload_cost);
+ LLNotificationsUtil::add("ErrorTextureCannotAfford", args);
+ }
closeFloater(false);
}
diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp
index 4eacd728c3..843dbbf25e 100644
--- a/indra/newview/llfloaterregioninfo.cpp
+++ b/indra/newview/llfloaterregioninfo.cpp
@@ -412,6 +412,11 @@ void LLFloaterRegionInfo::processRegionInfo(LLMessageSystem* msg)
panel->getChild<LLUICtrl>("object_bonus_spin")->setValue(LLSD(object_bonus_factor) );
panel->getChild<LLUICtrl>("access_combo")->setValue(LLSD(sim_access) );
+ LLPanelRegionGeneralInfo* panel_general = LLFloaterRegionInfo::getPanelGeneral();
+ if (panel)
+ {
+ panel_general->setObjBonusFactor(object_bonus_factor);
+ }
// detect teen grid for maturity
@@ -465,6 +470,16 @@ LLPanelEstateCovenant* LLFloaterRegionInfo::getPanelCovenant()
}
// static
+LLPanelRegionGeneralInfo* LLFloaterRegionInfo::getPanelGeneral()
+{
+ LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance<LLFloaterRegionInfo>("region_info");
+ if (!floater) return NULL;
+ LLTabContainer* tab = floater->getChild<LLTabContainer>("region_panels");
+ LLPanelRegionGeneralInfo* panel = (LLPanelRegionGeneralInfo*)tab->getChild<LLPanel>("General");
+ return panel;
+}
+
+// static
LLPanelRegionTerrainInfo* LLFloaterRegionInfo::getPanelRegionTerrain()
{
LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance<LLFloaterRegionInfo>("region_info");
@@ -717,7 +732,42 @@ BOOL LLPanelRegionGeneralInfo::postBuild()
childSetAction("im_btn", onClickMessage, this);
// childSetAction("manage_telehub_btn", onClickManageTelehub, this);
- return LLPanelRegionInfo::postBuild();
+ LLUICtrl* apply_btn = findChild<LLUICtrl>("apply_btn");
+ if (apply_btn)
+ {
+ apply_btn->setCommitCallback(boost::bind(&LLPanelRegionGeneralInfo::onBtnSet, this));
+ }
+
+ refresh();
+ return TRUE;
+}
+
+void LLPanelRegionGeneralInfo::onBtnSet()
+{
+ if(mObjBonusFactor == getChild<LLUICtrl>("object_bonus_spin")->getValue().asReal())
+ {
+ if (sendUpdate())
+ {
+ disableButton("apply_btn");
+ }
+ }
+ else
+ {
+ LLNotificationsUtil::add("ChangeObjectBonusFactor", LLSD(), LLSD(), boost::bind(&LLPanelRegionGeneralInfo::onChangeObjectBonus, this, _1, _2));
+ }
+}
+
+bool LLPanelRegionGeneralInfo::onChangeObjectBonus(const LLSD& notification, const LLSD& response)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (option == 0)
+ {
+ if (sendUpdate())
+ {
+ disableButton("apply_btn");
+ }
+ }
+ return false;
}
void LLPanelRegionGeneralInfo::onClickKick()
diff --git a/indra/newview/llfloaterregioninfo.h b/indra/newview/llfloaterregioninfo.h
index 46f2b42137..dbb0ad05e9 100644
--- a/indra/newview/llfloaterregioninfo.h
+++ b/indra/newview/llfloaterregioninfo.h
@@ -95,6 +95,7 @@ public:
static LLPanelEstateCovenant* getPanelCovenant();
static LLPanelRegionTerrainInfo* getPanelRegionTerrain();
static LLPanelRegionExperiences* getPanelExperiences();
+ static LLPanelRegionGeneralInfo* getPanelGeneral();
// from LLPanel
virtual void refresh();
@@ -183,6 +184,9 @@ public:
// LLPanel
virtual BOOL postBuild();
+ void onBtnSet();
+ void setObjBonusFactor(F32 object_bonus_factor) {mObjBonusFactor = object_bonus_factor;}
+
protected:
virtual BOOL sendUpdate();
void onClickKick();
@@ -191,6 +195,9 @@ protected:
bool onKickAllCommit(const LLSD& notification, const LLSD& response);
static void onClickMessage(void* userdata);
bool onMessageCommit(const LLSD& notification, const LLSD& response);
+ bool onChangeObjectBonus(const LLSD& notification, const LLSD& response);
+
+ F32 mObjBonusFactor;
};
diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp
index afec981d56..b906671c7f 100644
--- a/indra/newview/llfloatersnapshot.cpp
+++ b/indra/newview/llfloatersnapshot.cpp
@@ -1418,6 +1418,20 @@ void LLFloaterSnapshot::postPanelSwitch()
}
// static
+void LLFloaterSnapshot::inventorySaveFailed()
+{
+ LLFloaterSnapshot* instance = findInstance();
+ if (!instance)
+ {
+ llassert(instance != NULL);
+ return;
+ }
+
+ instance->impl.updateControls(instance);
+ instance->impl.setStatus(Impl::STATUS_FINISHED, false, "inventory");
+}
+
+// static
LLPointer<LLImageFormatted> LLFloaterSnapshot::getImageData()
{
// FIXME: May not work for textures.
diff --git a/indra/newview/llfloatersnapshot.h b/indra/newview/llfloatersnapshot.h
index 0bb9474bb5..eb3a94999b 100644
--- a/indra/newview/llfloatersnapshot.h
+++ b/indra/newview/llfloatersnapshot.h
@@ -61,6 +61,7 @@ public:
static BOOL saveLocal();
static void postSave();
static void postPanelSwitch();
+ static void inventorySaveFailed();
static LLPointer<LLImageFormatted> getImageData();
static const LLVector3d& getPosTakenGlobal();
static void setAgentEmail(const std::string& email);
diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp
index ece3e10faa..c67feb8158 100644
--- a/indra/newview/llfloaterworldmap.cpp
+++ b/indra/newview/llfloaterworldmap.cpp
@@ -963,10 +963,10 @@ F32 LLFloaterWorldMap::getDistanceToDestination(const LLVector3d &destination,
}
-void LLFloaterWorldMap::clearLocationSelection(BOOL clear_ui)
+void LLFloaterWorldMap::clearLocationSelection(BOOL clear_ui, BOOL dest_reached)
{
LLCtrlListInterface *list = mListSearchResults;
- if (list)
+ if (list && (!dest_reached || (list->getItemCount() == 1)))
{
list->operateOnAll(LLCtrlListInterface::OP_DELETE);
}
diff --git a/indra/newview/llfloaterworldmap.h b/indra/newview/llfloaterworldmap.h
index 7ce8dae9a9..c5801c8819 100644
--- a/indra/newview/llfloaterworldmap.h
+++ b/indra/newview/llfloaterworldmap.h
@@ -94,7 +94,7 @@ public:
// A z_attenuation of 0.0f collapses the distance into the X-Y plane
F32 getDistanceToDestination(const LLVector3d& pos_global, F32 z_attenuation = 0.5f) const;
- void clearLocationSelection(BOOL clear_ui = FALSE);
+ void clearLocationSelection(BOOL clear_ui = FALSE, BOOL dest_reached = FALSE);
void clearAvatarSelection(BOOL clear_ui = FALSE);
void clearLandmarkSelection(BOOL clear_ui = FALSE);
diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp
index e3cb4d57ef..d8f019374e 100644
--- a/indra/newview/llinventoryfunctions.cpp
+++ b/indra/newview/llinventoryfunctions.cpp
@@ -287,7 +287,11 @@ void update_marketplace_category(const LLUUID& cur_uuid, bool perform_consistenc
LL_INFOS("SLM") << "Unlist and clear version folder as the version folder is not at the right place anymore!!" << LL_ENDL;
LLMarketplaceData::instance().setVersionFolder(listing_uuid, LLUUID::null,1);
}
- else if (version_folder_uuid.notNull() && LLMarketplaceData::instance().getActivationState(version_folder_uuid) && (count_descendants_items(version_folder_uuid) == 0) && !LLMarketplaceData::instance().isUpdating(version_folder_uuid,version_depth))
+ else if (version_folder_uuid.notNull()
+ && gInventory.isCategoryComplete(version_folder_uuid)
+ && LLMarketplaceData::instance().getActivationState(version_folder_uuid)
+ && (count_descendants_items(version_folder_uuid) == 0)
+ && !LLMarketplaceData::instance().isUpdating(version_folder_uuid,version_depth))
{
LL_INFOS("SLM") << "Unlist as the version folder is empty of any item!!" << LL_ENDL;
LLNotificationsUtil::add("AlertMerchantVersionFolderEmpty");
diff --git a/indra/newview/lllocationinputctrl.cpp b/indra/newview/lllocationinputctrl.cpp
index 8d21fda8f9..53b2ca2b74 100644
--- a/indra/newview/lllocationinputctrl.cpp
+++ b/indra/newview/lllocationinputctrl.cpp
@@ -64,6 +64,9 @@
#include "llurllineeditorctrl.h"
#include "llagentui.h"
+#include "llmenuoptionpathfindingrebakenavmesh.h"
+#include "llpathfindingmanager.h"
+
//============================================================================
/*
* "ADD LANDMARK" BUTTON UPDATING LOGIC
@@ -1194,6 +1197,18 @@ bool LLLocationInputCtrl::onLocationContextMenuItemEnabled(const LLSD& userdata)
return false;
}
+void LLLocationInputCtrl::callbackRebakeRegion(const LLSD& notification, const LLSD& response)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (option == 0) // OK
+ {
+ if (LLPathfindingManager::getInstance() != NULL)
+ {
+ LLMenuOptionPathfindingRebakeNavmesh::getInstance()->sendRequestRebakeNavmesh();
+ }
+ }
+}
+
void LLLocationInputCtrl::onParcelIconClick(EParcelIcon icon)
{
switch (icon)
@@ -1211,6 +1226,16 @@ void LLLocationInputCtrl::onParcelIconClick(EParcelIcon icon)
LLNotificationsUtil::add("NoBuild");
break;
case PATHFINDING_DIRTY_ICON:
+ if (LLPathfindingManager::getInstance() != NULL)
+ {
+ LLMenuOptionPathfindingRebakeNavmesh *rebakeInstance = LLMenuOptionPathfindingRebakeNavmesh::getInstance();
+ if (rebakeInstance && rebakeInstance->canRebakeRegion() && (rebakeInstance->getMode() == LLMenuOptionPathfindingRebakeNavmesh::kRebakeNavMesh_Available))
+ {
+ LLNotificationsUtil::add("PathfindingDirtyRebake", LLSD(), LLSD(),
+ boost::bind(&LLLocationInputCtrl::callbackRebakeRegion, this, _1, _2));
+ break;
+ }
+ }
LLNotificationsUtil::add("PathfindingDirty");
break;
case PATHFINDING_DISABLED_ICON:
diff --git a/indra/newview/lllocationinputctrl.h b/indra/newview/lllocationinputctrl.h
index cd6fd24077..da71bab6c1 100644
--- a/indra/newview/lllocationinputctrl.h
+++ b/indra/newview/lllocationinputctrl.h
@@ -166,6 +166,7 @@ private:
// callbacks
bool onLocationContextMenuItemEnabled(const LLSD& userdata);
void onLocationContextMenuItemClicked(const LLSD& userdata);
+ void callbackRebakeRegion(const LLSD& notification, const LLSD& response);
void onParcelIconClick(EParcelIcon icon);
void createNavMeshStatusListenerForCurrentRegion();
diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp
index 4116e38f11..639641d1c2 100644
--- a/indra/newview/lllogchat.cpp
+++ b/indra/newview/lllogchat.cpp
@@ -904,7 +904,7 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params
std::string stuff = matches[IDX_STUFF];
boost::match_results<std::string::const_iterator> name_and_text;
if (!boost::regex_match(stuff, name_and_text, NAME_AND_TEXT)) return false;
-
+
bool has_name = name_and_text[IDX_NAME].matched;
std::string name = name_and_text[IDX_NAME];
@@ -956,7 +956,6 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params
im[LL_IM_FROM] = name;
}
-
im[LL_IM_TEXT] = name_and_text[IDX_TEXT];
return true; //parsed name and message text, maybe have a timestamp too
}
diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp
index 4229419fce..c779ba5cdd 100644
--- a/indra/newview/llpanelmaininventory.cpp
+++ b/indra/newview/llpanelmaininventory.cpp
@@ -150,6 +150,7 @@ BOOL LLPanelMainInventory::postBuild()
LLInventoryPanel* recent_items_panel = getChild<LLInventoryPanel>("Recent Items");
if (recent_items_panel)
{
+ // assign default values until we will be sure that we have setting to restore
recent_items_panel->setSinceLogoff(TRUE);
recent_items_panel->setSortOrder(LLInventoryFilter::SO_DATE);
recent_items_panel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS);
@@ -181,6 +182,7 @@ BOOL LLPanelMainInventory::postBuild()
LLParamSDParser parser;
parser.readSD(recent_items, p);
recent_items_panel->getFilter().fromParams(p);
+ recent_items_panel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::RECENTITEMS_SORT_ORDER));
}
}
@@ -372,7 +374,14 @@ void LLPanelMainInventory::setSortBy(const LLSD& userdata)
}
getActivePanel()->setSortOrder(sort_order_mask);
- gSavedSettings.setU32("InventorySortOrder", sort_order_mask);
+ if ("Recent Items" == getActivePanel()->getName())
+ {
+ gSavedSettings.setU32("RecentItemsSortOrder", sort_order_mask);
+ }
+ else
+ {
+ gSavedSettings.setU32("InventorySortOrder", sort_order_mask);
+ }
}
// static
@@ -1143,6 +1152,15 @@ void LLPanelMainInventory::onCustomAction(const LLSD& userdata)
}
}
+void LLPanelMainInventory::onVisibilityChange( BOOL new_visibility )
+{
+ if(!new_visibility)
+ {
+ mMenuAdd->setVisible(FALSE);
+ getActivePanel()->getRootFolder()->finishRenamingItem();
+ }
+}
+
bool LLPanelMainInventory::isSaveTextureEnabled(const LLSD& userdata)
{
LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem();
diff --git a/indra/newview/llpanelmaininventory.h b/indra/newview/llpanelmaininventory.h
index 21f0ca0cae..290e2e5f47 100644
--- a/indra/newview/llpanelmaininventory.h
+++ b/indra/newview/llpanelmaininventory.h
@@ -72,6 +72,7 @@ public:
std::string& tooltip_msg);
/*virtual*/ void changed(U32);
/*virtual*/ void draw();
+ /*virtual*/ void onVisibilityChange ( BOOL new_visibility );
LLInventoryPanel* getPanel() { return mActivePanel; }
LLInventoryPanel* getActivePanel() { return mActivePanel; }
diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp
index 73b928f014..bc177abc57 100644
--- a/indra/newview/llpanelpeople.cpp
+++ b/indra/newview/llpanelpeople.cpp
@@ -611,9 +611,11 @@ BOOL LLPanelPeople::postBuild()
mOnlineFriendList->setNoItemsCommentText(getString("no_friends_online"));
mOnlineFriendList->setShowIcons("FriendsListShowIcons");
mOnlineFriendList->showPermissions("FriendsListShowPermissions");
+ mOnlineFriendList->setShowCompleteName(!gSavedSettings.getBOOL("FriendsListHideUsernames"));
mAllFriendList->setNoItemsCommentText(getString("no_friends"));
mAllFriendList->setShowIcons("FriendsListShowIcons");
mAllFriendList->showPermissions("FriendsListShowPermissions");
+ mAllFriendList->setShowCompleteName(!gSavedSettings.getBOOL("FriendsListHideUsernames"));
LLPanel* nearby_tab = getChild<LLPanel>(NEARBY_TAB_NAME);
nearby_tab->setVisibleCallback(boost::bind(&Updater::setActive, mNearbyListUpdater, _2));
@@ -622,6 +624,7 @@ BOOL LLPanelPeople::postBuild()
mNearbyList->setNoItemsMsg(getString("no_one_near"));
mNearbyList->setNoFilteredItemsMsg(getString("no_one_filtered_near"));
mNearbyList->setShowIcons("NearbyListShowIcons");
+ mNearbyList->setShowCompleteName(!gSavedSettings.getBOOL("NearbyListHideUsernames"));
mMiniMap = (LLNetMap*)getChildView("Net Map",true);
mMiniMap->setToolTipMsg(gSavedSettings.getBOOL("DoubleClickTeleport") ?
getString("AltMiniMapToolTipMsg") : getString("MiniMapToolTipMsg"));
@@ -1342,6 +1345,16 @@ void LLPanelPeople::onFriendsViewSortMenuItemClicked(const LLSD& userdata)
mAllFriendList->showPermissions(show_permissions);
mOnlineFriendList->showPermissions(show_permissions);
}
+ else if (chosen_item == "view_usernames")
+ {
+ bool hide_usernames = !gSavedSettings.getBOOL("FriendsListHideUsernames");
+ gSavedSettings.setBOOL("FriendsListHideUsernames", hide_usernames);
+
+ mAllFriendList->setShowCompleteName(!hide_usernames);
+ mAllFriendList->handleDisplayNamesOptionChanged();
+ mOnlineFriendList->setShowCompleteName(!hide_usernames);
+ mOnlineFriendList->handleDisplayNamesOptionChanged();
+ }
}
void LLPanelPeople::onGroupsViewSortMenuItemClicked(const LLSD& userdata)
@@ -1374,6 +1387,14 @@ void LLPanelPeople::onNearbyViewSortMenuItemClicked(const LLSD& userdata)
{
setSortOrder(mNearbyList, E_SORT_BY_DISTANCE);
}
+ else if (chosen_item == "view_usernames")
+ {
+ bool hide_usernames = !gSavedSettings.getBOOL("NearbyListHideUsernames");
+ gSavedSettings.setBOOL("NearbyListHideUsernames", hide_usernames);
+
+ mNearbyList->setShowCompleteName(!hide_usernames);
+ mNearbyList->handleDisplayNamesOptionChanged();
+ }
}
bool LLPanelPeople::onNearbyViewSortMenuItemCheck(const LLSD& userdata)
diff --git a/indra/newview/llpanelpeoplemenus.cpp b/indra/newview/llpanelpeoplemenus.cpp
index a5f59dbf4a..65769ff526 100644
--- a/indra/newview/llpanelpeoplemenus.cpp
+++ b/indra/newview/llpanelpeoplemenus.cpp
@@ -38,9 +38,14 @@
#include "llavataractions.h"
#include "llcallingcard.h" // for LLAvatarTracker
#include "lllogchat.h"
+#include "llparcel.h"
#include "llviewermenu.h" // for gMenuHolder
#include "llconversationmodel.h"
#include "llviewerobjectlist.h"
+#include "llviewerparcelmgr.h"
+#include "llviewerregion.h"
+#include "llvoavatarself.h"
+#include "roles_constants.h"
namespace LLPanelPeopleMenus
{
@@ -77,9 +82,13 @@ LLContextMenu* PeopleContextMenu::createMenu()
registrar.add("Avatar.InviteToGroup", boost::bind(&LLAvatarActions::inviteToGroup, id));
registrar.add("Avatar.TeleportRequest", boost::bind(&PeopleContextMenu::requestTeleport, this));
registrar.add("Avatar.Calllog", boost::bind(&LLAvatarActions::viewChatHistory, id));
+ registrar.add("Avatar.Freeze", boost::bind(&LLAvatarActions::freezeAvatar, id));
+ registrar.add("Avatar.Eject", boost::bind(&PeopleContextMenu::eject, this));
+
enable_registrar.add("Avatar.EnableItem", boost::bind(&PeopleContextMenu::enableContextMenuItem, this, _2));
enable_registrar.add("Avatar.CheckItem", boost::bind(&PeopleContextMenu::checkContextMenuItem, this, _2));
+ enable_registrar.add("Avatar.EnableFreezeEject", boost::bind(&PeopleContextMenu::enableFreezeEject, this, _2));
// create the context menu from the XUI
menu = createFromFile("menu_people_nearby.xml");
@@ -258,6 +267,50 @@ bool PeopleContextMenu::checkContextMenuItem(const LLSD& userdata)
return false;
}
+bool PeopleContextMenu::enableFreezeEject(const LLSD& userdata)
+{
+ if((gAgent.getID() == mUUIDs.front()) || (mUUIDs.size() != 1))
+ {
+ return false;
+ }
+
+ const LLUUID& id = mUUIDs.front();
+
+ // Use avatar_id if available, otherwise default to right-click avatar
+ LLVOAvatar* avatar = NULL;
+ if (id.notNull())
+ {
+ LLViewerObject* object = gObjectList.findObject(id);
+ if (object)
+ {
+ if( !object->isAvatar() )
+ {
+ object = NULL;
+ }
+ avatar = (LLVOAvatar*) object;
+ }
+ }
+ if (!avatar) return false;
+
+ // Gods can always freeze
+ if (gAgent.isGodlike()) return true;
+
+ // Estate owners / managers can freeze
+ // Parcel owners can also freeze
+ const LLVector3& pos = avatar->getPositionRegion();
+ const LLVector3d& pos_global = avatar->getPositionGlobal();
+ LLParcel* parcel = LLViewerParcelMgr::getInstance()->selectParcelAt(pos_global)->getParcel();
+ LLViewerRegion* region = avatar->getRegion();
+ if (!region) return false;
+
+ bool new_value = region->isOwnedSelf(pos);
+ if (!new_value || region->isOwnedGroup(pos))
+ {
+ new_value = LLViewerParcelMgr::getInstance()->isParcelOwnedByAgent(parcel,GP_LAND_ADMIN);
+ }
+ return new_value;
+}
+
void PeopleContextMenu::requestTeleport()
{
// boost::bind cannot recognize overloaded method LLAvatarActions::teleportRequest(),
@@ -272,6 +325,39 @@ void PeopleContextMenu::offerTeleport()
LLAvatarActions::offerTeleport(mUUIDs);
}
+void PeopleContextMenu::eject()
+{
+ if((gAgent.getID() == mUUIDs.front()) || (mUUIDs.size() != 1))
+ {
+ return;
+ }
+
+ const LLUUID& id = mUUIDs.front();
+
+ // Use avatar_id if available, otherwise default to right-click avatar
+ LLVOAvatar* avatar = NULL;
+ if (id.notNull())
+ {
+ LLViewerObject* object = gObjectList.findObject(id);
+ if (object)
+ {
+ if( !object->isAvatar() )
+ {
+ object = NULL;
+ }
+ avatar = (LLVOAvatar*) object;
+ }
+ }
+ if (!avatar) return;
+ LLSD payload;
+ payload["avatar_id"] = avatar->getID();
+ std::string fullname = avatar->getFullname();
+
+ const LLVector3d& pos = avatar->getPositionGlobal();
+ LLParcel* parcel = LLViewerParcelMgr::getInstance()->selectParcelAt(pos)->getParcel();
+ LLAvatarActions::ejectAvatar(id ,LLViewerParcelMgr::getInstance()->isParcelOwnedByAgent(parcel,GP_LAND_MANAGE_BANNED));
+}
+
void PeopleContextMenu::startConference()
{
uuid_vec_t uuids;
@@ -320,6 +406,8 @@ void NearbyPeopleContextMenu::buildContextMenu(class LLMenuGL& menu, U32 flags)
items.push_back(std::string("share"));
items.push_back(std::string("pay"));
items.push_back(std::string("block_unblock"));
+ items.push_back(std::string("freeze"));
+ items.push_back(std::string("eject"));
}
hide_context_entries(menu, items, disabled_items);
diff --git a/indra/newview/llpanelpeoplemenus.h b/indra/newview/llpanelpeoplemenus.h
index 9767bab89f..5ed20e0064 100644
--- a/indra/newview/llpanelpeoplemenus.h
+++ b/indra/newview/llpanelpeoplemenus.h
@@ -46,7 +46,9 @@ protected:
private:
bool enableContextMenuItem(const LLSD& userdata);
bool checkContextMenuItem(const LLSD& userdata);
+ bool enableFreezeEject(const LLSD& userdata);
void offerTeleport();
+ void eject();
void startConference();
void requestTeleport();
};
diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp
index e795e7eedb..184238c40c 100644
--- a/indra/newview/llpanelprofile.cpp
+++ b/indra/newview/llpanelprofile.cpp
@@ -176,6 +176,16 @@ public:
return true;
}
+ if (verb == "unblock")
+ {
+ if (params.size() > 2)
+ {
+ const std::string object_name = params[2].asString();
+ LLMute mute(avatar_id, object_name, LLMute::OBJECT);
+ LLMuteList::getInstance()->remove(mute);
+ }
+ return true;
+ }
return false;
}
};
diff --git a/indra/newview/llpanelsnapshotinventory.cpp b/indra/newview/llpanelsnapshotinventory.cpp
index c55e230b5e..a2d1752c6a 100644
--- a/indra/newview/llpanelsnapshotinventory.cpp
+++ b/indra/newview/llpanelsnapshotinventory.cpp
@@ -34,6 +34,8 @@
#include "llfloatersnapshot.h" // FIXME: replace with a snapshot storage model
#include "llpanelsnapshot.h"
#include "llviewercontrol.h" // gSavedSettings
+#include "llstatusbar.h" // can_afford_transaction()
+#include "llnotificationsutil.h"
/**
* The panel provides UI for saving snapshot as an inventory texture.
@@ -102,6 +104,17 @@ void LLPanelSnapshotInventory::onResolutionCommit(LLUICtrl* ctrl)
void LLPanelSnapshotInventory::onSend()
{
- LLFloaterSnapshot::saveTexture();
- LLFloaterSnapshot::postSave();
+ S32 expected_upload_cost = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload();
+ if (can_afford_transaction(expected_upload_cost))
+ {
+ LLFloaterSnapshot::saveTexture();
+ LLFloaterSnapshot::postSave();
+ }
+ else
+ {
+ LLSD args;
+ args["COST"] = llformat("%d", expected_upload_cost);
+ LLNotificationsUtil::add("ErrorPhotoCannotAfford", args);
+ LLFloaterSnapshot::inventorySaveFailed();
+ }
}
diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp
index d86a8b4480..d0353259a5 100644
--- a/indra/newview/llpanelwearing.cpp
+++ b/indra/newview/llpanelwearing.cpp
@@ -94,6 +94,7 @@ protected:
LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
registrar.add("Wearing.Edit", boost::bind(&edit_outfit));
+ registrar.add("Wearing.ShowOriginal", boost::bind(show_item_original, mUUIDs.front()));
registrar.add("Wearing.TakeOff",
boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs));
registrar.add("Wearing.Detach",
@@ -144,6 +145,7 @@ protected:
menu->setItemVisible("take_off", allow_take_off);
menu->setItemVisible("detach", allow_detach);
menu->setItemVisible("edit_outfit_separator", allow_take_off || allow_detach);
+ menu->setItemVisible("show_original", mUUIDs.size() == 1);
}
};
diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp
index 20c43bc432..ba9845ef04 100644
--- a/indra/newview/llpreviewnotecard.cpp
+++ b/indra/newview/llpreviewnotecard.cpp
@@ -94,7 +94,8 @@ BOOL LLPreviewNotecard::postBuild()
if (item)
{
getChild<LLUICtrl>("desc")->setValue(item->getDescription());
- getChildView("Delete")->setEnabled(true);
+ BOOL source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID());
+ getChildView("Delete")->setEnabled(!source_library);
}
getChild<LLLineEditor>("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe);
@@ -219,6 +220,7 @@ void LLPreviewNotecard::loadAsset()
BOOL is_owner = gAgent.allowOperation(PERM_OWNER, perm, GP_OBJECT_MANIPULATE);
BOOL allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE);
BOOL allow_modify = canModify(mObjectUUID, item);
+ BOOL source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
if (allow_copy || gAgent.isGodlike())
{
@@ -288,7 +290,7 @@ void LLPreviewNotecard::loadAsset()
getChildView("lock")->setVisible( TRUE);
}
- if(allow_modify || is_owner)
+ if((allow_modify || is_owner) && !source_library)
{
getChildView("Delete")->setEnabled(TRUE);
}
diff --git a/indra/newview/llpreviewtexture.cpp b/indra/newview/llpreviewtexture.cpp
index 2a2c51be40..645a77e42a 100644
--- a/indra/newview/llpreviewtexture.cpp
+++ b/indra/newview/llpreviewtexture.cpp
@@ -38,6 +38,7 @@
#include "llimagetga.h"
#include "llimagepng.h"
#include "llinventory.h"
+#include "llinventorymodel.h"
#include "llnotificationsutil.h"
#include "llresmgr.h"
#include "lltrans.h"
@@ -120,18 +121,22 @@ BOOL LLPreviewTexture::postBuild()
childSetAction("save_tex_btn", LLPreviewTexture::onSaveAsBtn, this);
getChildView("save_tex_btn")->setVisible( true);
getChildView("save_tex_btn")->setEnabled(canSaveAs());
-
- if (!mCopyToInv)
- {
- const LLInventoryItem* item = getItem();
-
- if (item)
- {
- childSetCommitCallback("desc", LLPreview::onText, this);
- getChild<LLUICtrl>("desc")->setValue(item->getDescription());
- getChild<LLLineEditor>("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe);
- }
- }
+
+ const LLInventoryItem* item = getItem();
+ if (item)
+ {
+ if (!mCopyToInv)
+ {
+ childSetCommitCallback("desc", LLPreview::onText, this);
+ getChild<LLUICtrl>("desc")->setValue(item->getDescription());
+ getChild<LLLineEditor>("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe);
+ }
+ BOOL source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID());
+ if (source_library)
+ {
+ getChildView("Discard")->setEnabled(false);
+ }
+ }
// Fill in ratios list with common aspect ratio values
mRatiosList.clear();
@@ -526,6 +531,15 @@ void LLPreviewTexture::loadAsset()
// check that we can copy inworld items into inventory
getChildView("Keep")->setEnabled(mIsCopyable);
}
+ else
+ {
+ // check that we can remove item
+ BOOL source_library = gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
+ if (source_library)
+ {
+ getChildView("Discard")->setEnabled(false);
+ }
+ }
}
LLPreview::EAssetStatus LLPreviewTexture::getAssetStatus()
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 88fbd233b8..a2c8e7772e 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -687,6 +687,11 @@ bool idle_startup()
gRememberPassword = gSavedSettings.getBOOL("RememberPassword");
show_connect_box = TRUE;
}
+
+ //setup map of datetime strings to codes and slt & local time offset from utc
+ // *TODO: Does this need to be here?
+ LLStringOps::setupDatetimeInfo(false);
+
// Go to the next startup state
LLStartUp::setStartupState( STATE_BROWSER_INIT );
return FALSE;
@@ -1139,9 +1144,6 @@ bool idle_startup()
LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done);
}
}
- //setup map of datetime strings to codes and slt & local time offset from utc
- // *TODO: Does this need to be here?
- LLStringOps::setupDatetimeInfo (false);
transition_back_to_login_panel(emsg.str());
show_connect_box = true;
}
@@ -3310,6 +3312,13 @@ bool process_login_success_response()
{
time_t now = time(NULL);
gUTCOffset = (server_utc_time - now);
+
+ // Print server timestamp
+ LLSD substitution;
+ substitution["datetime"] = (S32)server_utc_time;
+ std::string timeStr = "[month, datetime, slt] [day, datetime, slt] [year, datetime, slt] [hour, datetime, slt]:[min, datetime, slt]:[second, datetime, slt]";
+ LLStringUtil::format(timeStr, substitution);
+ LL_INFOS("AppInit") << "Server SLT timestamp: " << timeStr << ". Server-viewer time offset before correction: " << gUTCOffset << "s" << LL_ENDL;
}
}
diff --git a/indra/newview/lltoastnotifypanel.cpp b/indra/newview/lltoastnotifypanel.cpp
index 98ed2f0fc4..e3a856be5c 100644
--- a/indra/newview/lltoastnotifypanel.cpp
+++ b/indra/newview/lltoastnotifypanel.cpp
@@ -103,7 +103,7 @@ LLButton* LLToastNotifyPanel::createButton(const LLSD& form_element, BOOL is_opt
p.image_color_disabled(LLUIColorTable::instance().getColor("ButtonCautionImageColor"));
}
// for the scriptdialog buttons we use fixed button size. This is a limit!
- if (!mIsScriptDialog && font->getWidth(form_element["text"].asString()) > BUTTON_WIDTH)
+ if (!mIsScriptDialog && font->getWidth(form_element["text"].asString()) > (BUTTON_WIDTH-2*HPAD))
{
p.rect.width = 1;
p.auto_resize = true;
@@ -160,7 +160,11 @@ void LLToastNotifyPanel::updateButtonsLayout(const std::vector<index_button_pair
}
LLButton* btn = it->second;
LLRect btn_rect(btn->getRect());
- if (left + btn_rect.getWidth() > max_width)// whether there is still some place for button+h_pad in the mControlPanel
+ if (buttons.size() == 1) // for the one-button forms, center that button
+ {
+ left = (max_width - btn_rect.getWidth()) / 2;
+ }
+ else if (left + btn_rect.getWidth() > max_width)// whether there is still some place for button+h_pad in the mControlPanel
{
// looks like we need to add button to the next row
left = 0;
@@ -321,6 +325,7 @@ void LLToastNotifyPanel::init( LLRect rect, bool show_images )
mTextBox->setContentTrusted(is_content_trusted);
mTextBox->setValue(mNotification->getMessage());
mTextBox->setIsFriendCallback(LLAvatarActions::isFriend);
+ mTextBox->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0));
// add buttons for a script notification
if (mIsTip)
diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp
index 2f8e464b71..b0e3b5bf89 100644
--- a/indra/newview/lltoolmgr.cpp
+++ b/indra/newview/lltoolmgr.cpp
@@ -83,6 +83,7 @@ LLToolMgr::LLToolMgr()
// Not a panel, register these callbacks globally.
LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Active", boost::bind(&LLToolMgr::inEdit, this));
LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Enabled", boost::bind(&LLToolMgr::canEdit, this));
+ LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.EnabledOrActive", boost::bind(&LLToolMgr::buildEnabledOrActive, this));
LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Build.Toggle", boost::bind(&LLToolMgr::toggleBuildMode, this, _2));
LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Marketplace.Enabled", boost::bind(&LLToolMgr::canAccessMarketplace, this));
LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Marketplace.Toggle", boost::bind(&LLToolMgr::toggleMarketplace, this, _2));
@@ -264,17 +265,21 @@ bool LLToolMgr::canEdit()
return LLViewerParcelMgr::getInstance()->allowAgentBuild();
}
+bool LLToolMgr::buildEnabledOrActive()
+{
+ return inEdit() || canEdit();
+}
+
void LLToolMgr::toggleBuildMode(const LLSD& sdname)
{
const std::string& param = sdname.asString();
+ LLFloaterReg::toggleInstanceOrBringToFront("build");
if (param == "build" && !canEdit())
{
return;
}
- LLFloaterReg::toggleInstanceOrBringToFront("build");
-
bool build_visible = LLFloaterReg::instanceVisible("build");
if (build_visible)
{
diff --git a/indra/newview/lltoolmgr.h b/indra/newview/lltoolmgr.h
index a3c1045aac..e5b45750d9 100644
--- a/indra/newview/lltoolmgr.h
+++ b/indra/newview/lltoolmgr.h
@@ -54,6 +54,7 @@ public:
bool inEdit();
bool canEdit();
+ bool buildEnabledOrActive();
bool canAccessMarketplace();
void toggleBuildMode(const LLSD& sdname);
void toggleMarketplace(const LLSD& sdname);
diff --git a/indra/newview/lltracker.cpp b/indra/newview/lltracker.cpp
index f611d0503f..b015cde45d 100644
--- a/indra/newview/lltracker.cpp
+++ b/indra/newview/lltracker.cpp
@@ -183,7 +183,7 @@ void LLTracker::render3D()
F32 dist = gFloaterWorldMap->getDistanceToDestination(pos_global, 0.5f);
if (dist < DESTINATION_REACHED_RADIUS)
{
- instance()->stopTrackingLocation();
+ instance()->stopTrackingLocation(FALSE,TRUE);
}
else
{
@@ -655,13 +655,13 @@ void LLTracker::stopTrackingLandmark(BOOL clear_ui)
}
-void LLTracker::stopTrackingLocation(BOOL clear_ui)
+void LLTracker::stopTrackingLocation(BOOL clear_ui, BOOL dest_reached)
{
purgeBeaconText();
mTrackedLocationName.assign("");
mIsTrackingLocation = FALSE;
mTrackedPositionGlobal.zeroVec();
- gFloaterWorldMap->clearLocationSelection(clear_ui);
+ gFloaterWorldMap->clearLocationSelection(clear_ui, dest_reached);
mTrackingStatus = TRACKING_NOTHING;
mTrackingLocationType = LOCATION_NOTHING;
}
diff --git a/indra/newview/lltracker.h b/indra/newview/lltracker.h
index 218f3430a6..a1c5052c1b 100644
--- a/indra/newview/lltracker.h
+++ b/indra/newview/lltracker.h
@@ -116,7 +116,7 @@ protected:
void stopTrackingAll(BOOL clear_ui = FALSE);
void stopTrackingAvatar(BOOL clear_ui = FALSE);
- void stopTrackingLocation(BOOL clear_ui = FALSE);
+ void stopTrackingLocation(BOOL clear_ui = FALSE, BOOL dest_reached = FALSE);
void stopTrackingLandmark(BOOL clear_ui = FALSE);
void drawMarker(const LLVector3d& pos_global, const LLColor4& color);
diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp
index f0dafec240..497ff4d2bf 100644
--- a/indra/newview/llviewerassetupload.cpp
+++ b/indra/newview/llviewerassetupload.cpp
@@ -837,5 +837,12 @@ void LLViewerAssetUpload::HandleUploadError(LLCore::HttpStatus status, LLSD &res
}
}
+ // Let the Snapshot floater know we have failed uploading.
+ LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot");
+ if (uploadInfo->getAssetType() == LLAssetType::AT_TEXTURE && floater_snapshot)
+ {
+ floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", false).with("msg", "inventory")));
+ }
+
}
diff --git a/indra/newview/llviewerpartsource.cpp b/indra/newview/llviewerpartsource.cpp
index 7efa821bbf..814060f4f2 100644
--- a/indra/newview/llviewerpartsource.cpp
+++ b/indra/newview/llviewerpartsource.cpp
@@ -441,10 +441,20 @@ LLPointer<LLViewerPartSourceScript> LLViewerPartSourceScript::unpackPSS(LLViewer
return NULL;
}
+ F32 prev_max_age = pssp->mPartSysData.mMaxAge;
+ F32 prev_start_age = pssp->mPartSysData.mStartAge;
if (!pssp->mPartSysData.unpackBlock(block_num))
{
return NULL;
}
+ else if (pssp->mPartSysData.mMaxAge
+ && (prev_max_age != pssp->mPartSysData.mMaxAge || prev_start_age != pssp->mPartSysData.mStartAge))
+ {
+ // reusing existing pss, so reset time to allow particles to start again
+ pssp->mLastUpdateTime = 0.f;
+ pssp->mLastPartTime = 0.f;
+ }
+
if (pssp->mPartSysData.mTargetUUID.notNull())
{
LLViewerObject *target_objp = gObjectList.findObject(pssp->mPartSysData.mTargetUUID);
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index cac2ed8585..899ab3a371 100644
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -264,17 +264,18 @@ void LLViewerRegionImpl::requestBaseCapabilitiesCoro(U64 regionHandle)
}
S32 id = ++mHttpResponderID;
- ++mSeedCapAttempts;
LLSD capabilityNames = LLSD::emptyArray();
buildCapabilityNames(capabilityNames);
LL_INFOS("AppInit", "Capabilities") << "Requesting seed from " << url
- << " (attempt #" << mSeedCapAttempts << ")" << LL_ENDL;
+ << " (attempt #" << mSeedCapAttempts + 1 << ")" << LL_ENDL;
regionp = NULL;
result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames);
+ ++mSeedCapAttempts;
+
regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
if (!regionp) //region was removed
{
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index db4b555eca..ed719ae418 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -1875,7 +1875,8 @@ bool LLViewerFetchedTexture::updateFetch()
static LLCachedControl<bool> textures_decode_disabled(gSavedSettings,"TextureDecodeDisabled", false);
static LLCachedControl<F32> sCameraMotionThreshold(gSavedSettings,"TextureCameraMotionThreshold", 0.2);
static LLCachedControl<S32> sCameraMotionBoost(gSavedSettings,"TextureCameraMotionBoost", 3);
- if(textures_decode_disabled)
+ if(textures_decode_disabled ||
+ (gUseWireframe && mBoostLevel < LLGLTexture::BOOST_AVATAR_BAKED_SELF)) // don't fetch the surface textures in wireframe mode
{
return false;
}
diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp
index 08f6143861..d7080051da 100644
--- a/indra/newview/llviewertexturelist.cpp
+++ b/indra/newview/llviewertexturelist.cpp
@@ -171,13 +171,27 @@ void LLViewerTextureList::doPreloadImages()
mImagePreloads.insert(image);
}
image = LLViewerTextureManager::getFetchedTextureFromFile("transparent.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE,
- 0,0,LLUUID("8dcd4a48-2d37-4909-9f78-f7a9eb4ef903"));
+ 0, 0, IMG_TRANSPARENT);
if (image)
{
image->setAddressMode(LLTexUnit::TAM_WRAP);
mImagePreloads.insert(image);
}
-
+ image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient.tga", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE,
+ GL_ALPHA8, GL_ALPHA, IMG_ALPHA_GRAD);
+ if (image)
+ {
+ image->setAddressMode(LLTexUnit::TAM_CLAMP);
+ mImagePreloads.insert(image);
+ }
+ image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient_2d.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE,
+ GL_ALPHA8, GL_ALPHA, IMG_ALPHA_GRAD_2D);
+ if (image)
+ {
+ image->setAddressMode(LLTexUnit::TAM_CLAMP);
+ mImagePreloads.insert(image);
+ }
+
LLPointer<LLImageRaw> img_blak_square_tex(new LLImageRaw(2, 2, 3));
memset(img_blak_square_tex->getData(), 0, img_blak_square_tex->getDataSize());
LLPointer<LLViewerFetchedTexture> img_blak_square(new LLViewerFetchedTexture(img_blak_square_tex, FTT_DEFAULT, FALSE));
@@ -188,7 +202,7 @@ void LLViewerTextureList::doPreloadImages()
static std::string get_texture_list_name()
{
- return gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "texture_list_" + gSavedSettings.getString("LoginLocation") + ".xml");
+ return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "texture_list_" + gSavedSettings.getString("LoginLocation") + "." + gDirUtilp->getUserName() + ".xml");
}
void LLViewerTextureList::doPrefetchImages()
@@ -293,7 +307,7 @@ void LLViewerTextureList::shutdown()
break;
}
- if (count > 0 && !gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "").empty())
+ if (count > 0 && !gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "").empty())
{
std::string filename = get_texture_list_name();
llofstream file;
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index b9dd43f061..672d153e51 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -7313,7 +7313,6 @@ bool resolve_appearance_version(const LLAppearanceMessageContents& contents, S32
//-----------------------------------------------------------------------------
void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys )
{
- static S32 largestSelfCOFSeen(LLViewerInventoryCategory::VERSION_UNKNOWN);
LL_DEBUGS("Avatar") << "starts" << LL_ENDL;
bool enable_verbose_dumps = gSavedSettings.getBOOL("DebugAvatarAppearanceMessage");
@@ -7348,43 +7347,34 @@ void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys )
return;
}
- S32 this_update_cof_version = contents.mCOFVersion;
- S32 last_update_request_cof_version = mLastUpdateRequestCOFVersion;
+ S32 thisAppearanceVersion(contents.mCOFVersion);
+ if (isSelf())
+ { // In the past this was considered to be the canonical COF version,
+ // that is no longer the case. The canonical version is maintained
+ // by the AIS code and should match the COF version there. Even so,
+ // we must prevent rolling this one backwards backwards or processing
+ // stale versions.
- if( isSelf() )
- {
- LL_DEBUGS("Avatar") << "this_update_cof_version " << this_update_cof_version
- << " last_update_request_cof_version " << last_update_request_cof_version
- << " my_cof_version " << LLAppearanceMgr::instance().getCOFVersion() << LL_ENDL;
+ S32 aisCOFVersion(LLAppearanceMgr::instance().getCOFVersion());
+
+ LL_DEBUGS("Avatar") << "handling self appearance message #" << thisAppearanceVersion <<
+ " (highest seen #" << mLastUpdateReceivedCOFVersion <<
+ ") (AISCOF=#" << aisCOFVersion << ")" << LL_ENDL;
- if (largestSelfCOFSeen > this_update_cof_version)
+ if (mLastUpdateReceivedCOFVersion >= thisAppearanceVersion)
{
- LL_WARNS("Avatar") << "Already processed appearance for COF version " <<
- largestSelfCOFSeen << ", discarding appearance with COF " << this_update_cof_version << LL_ENDL;
+ LL_WARNS("Avatar") << "Stale appearance received #" << thisAppearanceVersion <<
+ " attempt to roll back from #" << mLastUpdateReceivedCOFVersion <<
+ "... dropping." << LL_ENDL;
+ return;
+ }
+ if (isEditingAppearance())
+ {
+ LL_DEBUGS("Avatar") << "Editing appearance. Dropping appearance update." << LL_ENDL;
return;
}
- largestSelfCOFSeen = this_update_cof_version;
-
- }
- else
- {
- LL_DEBUGS("Avatar") << "appearance message received" << LL_ENDL;
- }
-
- // Check for stale update.
- if (isSelf()
- && (this_update_cof_version < last_update_request_cof_version))
- {
- LL_WARNS() << "Stale appearance update, wanted version " << last_update_request_cof_version
- << ", got " << this_update_cof_version << LL_ENDL;
- return;
- }
- if (isSelf() && isEditingAppearance())
- {
- LL_DEBUGS("Avatar") << "ignoring appearance message while in appearance edit" << LL_ENDL;
- return;
- }
+ }
// SUNSHINE CLEANUP - is this case OK now?
S32 num_params = contents.mParamWeights.size();
@@ -7399,13 +7389,17 @@ void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys )
}
// No backsies zone - if we get here, the message should be valid and usable, will be processed.
- LL_INFOS("Avatar") << "Processing appearance message version " << this_update_cof_version << LL_ENDL;
+ LL_INFOS("Avatar") << "Processing appearance message version " << thisAppearanceVersion << LL_ENDL;
- // Note:
- // RequestAgentUpdateAppearanceResponder::onRequestRequested()
- // assumes that cof version is only updated with server-bake
- // appearance messages.
- mLastUpdateReceivedCOFVersion = this_update_cof_version;
+ if (isSelf())
+ {
+ // Note:
+ // locally the COF is maintained via LLInventoryModel::accountForUpdate
+ // which is called from various places. This should match the simhost's
+ // idea of what the COF version is. AIS however maintains its own version
+ // of the COF that should be considered canonical.
+ mLastUpdateReceivedCOFVersion = thisAppearanceVersion;
+ }
if (applyParsedTEMessage(contents.mTEContents) > 0 && isChanged(TEXTURE))
{
@@ -7528,7 +7522,7 @@ void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys )
// Got an update for some other avatar
// Ignore updates for self, because we have a more authoritative value in the preferences.
setHoverOffset(contents.mHoverOffset);
- LL_INFOS("Avatar") << avString() << "setting hover to " << contents.mHoverOffset[2] << LL_ENDL;
+ LL_DEBUGS("Avatar") << avString() << "setting hover to " << contents.mHoverOffset[2] << LL_ENDL;
}
if (!contents.mHoverOffsetWasSet && !isSelf())
diff --git a/indra/newview/skins/default/xui/en/fonts.xml b/indra/newview/skins/default/xui/en/fonts.xml
index 170b7177fb..5d05ecf127 100644
--- a/indra/newview/skins/default/xui/en/fonts.xml
+++ b/indra/newview/skins/default/xui/en/fonts.xml
@@ -12,6 +12,7 @@
<os name="Mac">
<file>ヒラギノ角ゴ Pro W3.otf</file>
<file>ヒラギノ角ゴ ProN W3.otf</file>
+ <file>ヒラギノ明朝 ProN W3.ttc</file>
<file>AppleGothic.dfont</file>
<file>AppleGothic.ttf</file>
<file>AppleSDGothicNeo-Regular.otf</file>
diff --git a/indra/newview/skins/default/xui/en/menu_login.xml b/indra/newview/skins/default/xui/en/menu_login.xml
index 419ec359a6..dcf2da52f1 100644
--- a/indra/newview/skins/default/xui/en/menu_login.xml
+++ b/indra/newview/skins/default/xui/en/menu_login.xml
@@ -140,13 +140,6 @@
function="Advanced.ShowDebugSettings"
parameter="all" />
</menu_item_call>
- <menu_item_call
- label="UI/Color Settings"
- name="UI/Color Settings">
- <menu_item_call.on_click
- function="Advanced.ShowDebugSettings"
- parameter="skin" />
- </menu_item_call>
<menu_item_separator />
<menu_item_call
label="XUI Preview Tool"
diff --git a/indra/newview/skins/default/xui/en/menu_object_icon.xml b/indra/newview/skins/default/xui/en/menu_object_icon.xml
index 2d4f1792c2..5137aea72a 100644
--- a/indra/newview/skins/default/xui/en/menu_object_icon.xml
+++ b/indra/newview/skins/default/xui/en/menu_object_icon.xml
@@ -23,6 +23,20 @@
<menu_item_call.on_click
function="ObjectIcon.Action"
parameter="block" />
+ <menu_item_call.on_visible
+ function="ObjectIcon.Visible"
+ parameter="not_blocked" />
+ </menu_item_call>
+ <menu_item_call
+ label="Unblock"
+ layout="topleft"
+ name="Unblock">
+ <menu_item_call.on_click
+ function="ObjectIcon.Action"
+ parameter="unblock" />
+ <menu_item_call.on_visible
+ function="ObjectIcon.Visible"
+ parameter="is_blocked" />
</menu_item_call>
<menu_item_separator
layout="topleft" />
diff --git a/indra/newview/skins/default/xui/en/menu_people_friends_view.xml b/indra/newview/skins/default/xui/en/menu_people_friends_view.xml
index 8790fde7c5..b5a4b87acd 100644
--- a/indra/newview/skins/default/xui/en/menu_people_friends_view.xml
+++ b/indra/newview/skins/default/xui/en/menu_people_friends_view.xml
@@ -40,6 +40,14 @@
function="CheckControl"
parameter="FriendsListShowPermissions" />
</menu_item_check>
+ <menu_item_check name="view_usernames" label="Hide usernames">
+ <menu_item_check.on_click
+ function="People.Friends.ViewSort.Action"
+ parameter="view_usernames" />
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="FriendsListHideUsernames" />
+ </menu_item_check>
<menu_item_check name="view_conversation" label="View Conversation Log...">
<menu_item_check.on_check
function="Floater.Visible"
diff --git a/indra/newview/skins/default/xui/en/menu_people_nearby.xml b/indra/newview/skins/default/xui/en/menu_people_nearby.xml
index f12226ebeb..c1500d4e7c 100644
--- a/indra/newview/skins/default/xui/en/menu_people_nearby.xml
+++ b/indra/newview/skins/default/xui/en/menu_people_nearby.xml
@@ -143,4 +143,20 @@
function="Avatar.EnableItem"
parameter="can_block" />
</menu_item_check>
+ <menu_item_call
+ label="Freeze"
+ name="freeze">
+ <menu_item_call.on_click
+ function="Avatar.Freeze" />
+ <menu_item_call.on_visible
+ function="Avatar.EnableFreezeEject"/>
+ </menu_item_call>
+ <menu_item_call
+ label="Eject"
+ name="eject">
+ <menu_item_call.on_click
+ function="Avatar.Eject" />
+ <menu_item_call.on_visible
+ function="Avatar.EnableFreezeEject"/>
+ </menu_item_call>
</context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_people_nearby_view.xml b/indra/newview/skins/default/xui/en/menu_people_nearby_view.xml
index da88ca9f4d..a9f6b8045d 100644
--- a/indra/newview/skins/default/xui/en/menu_people_nearby_view.xml
+++ b/indra/newview/skins/default/xui/en/menu_people_nearby_view.xml
@@ -50,4 +50,12 @@
function="ToggleControl"
parameter="NearbyListShowMap" />
</menu_item_check>
+ <menu_item_check name="view_usernames" label="Hide usernames">
+ <menu_item_check.on_click
+ function="People.Nearby.ViewSort.Action"
+ parameter="view_usernames" />
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="NearbyListHideUsernames" />
+ </menu_item_check>
</toggleable_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_url_objectim.xml b/indra/newview/skins/default/xui/en/menu_url_objectim.xml
index b9d003b841..41d40b389a 100644
--- a/indra/newview/skins/default/xui/en/menu_url_objectim.xml
+++ b/indra/newview/skins/default/xui/en/menu_url_objectim.xml
@@ -16,6 +16,13 @@
<menu_item_call.on_click
function="Url.Block" />
</menu_item_call>
+ <menu_item_call
+ label="Unblock"
+ layout="topleft"
+ name="unblock_object">
+ <menu_item_call.on_click
+ function="Url.Unblock" />
+ </menu_item_call>
<menu_item_separator
layout="topleft" />
<menu_item_call
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 0a492fb37b..b189d1038f 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -806,7 +806,7 @@
<menu_item_check.on_click
function="Build.Toggle" />
<menu_item_check.on_enable
- function="Build.Enabled" />
+ function="Build.EnabledOrActive" />
</menu_item_check>
<menu
create_jump_keys="true"
diff --git a/indra/newview/skins/default/xui/en/menu_wearing_tab.xml b/indra/newview/skins/default/xui/en/menu_wearing_tab.xml
index 2d54e69601..44b2727671 100644
--- a/indra/newview/skins/default/xui/en/menu_wearing_tab.xml
+++ b/indra/newview/skins/default/xui/en/menu_wearing_tab.xml
@@ -27,4 +27,11 @@
<on_click
function="Wearing.Edit" />
</menu_item_call>
+ <menu_item_call
+ label="Show Original"
+ layout="topleft"
+ name="show_original">
+ <on_click
+ function="Wearing.ShowOriginal" />
+ </menu_item_call>
</context_menu>
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 492d963653..dfde38bc5f 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -1150,6 +1150,22 @@ Error encoding snapshot.
<notification
icon="alertmodal.tga"
+ name="ErrorPhotoCannotAfford"
+ type="alertmodal">
+ You need L$[COST] to save a photo to your inventory. You may either buy L$ or save the photo to your computer instead.
+ <tag>fail</tag>
+ </notification>
+
+ <notification
+ icon="alertmodal.tga"
+ name="ErrorTextureCannotAfford"
+ type="alertmodal">
+ You need L$[COST] to save a texture to your inventory. You may either buy L$ or save the photo to your computer instead.
+ <tag>fail</tag>
+ </notification>
+
+ <notification
+ icon="alertmodal.tga"
name="ErrorUploadingPostcard"
type="alertmodal">
There was a problem sending a snapshot due to the following reason: [REASON]
@@ -3523,6 +3539,19 @@ Teleport all Residents in this region home?
<notification
icon="alertmodal.tga"
+ name="ChangeObjectBonusFactor"
+ type="alertmodal">
+ Lowering the object bonus after builds have been established in a region may cause objects to be returned or deleted. Are you sure you want to change object bonus?
+ <tag>confirm</tag>
+ <usetemplate
+ ignoretext="Confirm changing object bonus factor"
+ name="okcancelignore"
+ notext="Cancel"
+ yestext="OK"/>
+ </notification>
+
+ <notification
+ icon="alertmodal.tga"
name="EstateObjectReturn"
type="alertmodal">
Are you sure you want to return objects owned by [USER_NAME]?
@@ -6881,6 +6910,19 @@ This area has building disabled. You can&apos;t build or rez objects here.
</notification>
<notification
+ icon="notify.tga"
+ name="PathfindingDirtyRebake"
+ persist="true"
+ type="notify">
+ <unique/>
+ The region has pending pathfinding changes. If you have build rights, you may rebake the region by clicking on the “Rebake region” button.
+ <usetemplate
+ name="okbutton"
+ yestext="Rebake region"
+ />
+ </notification>
+
+ <notification
icon="notify.tga"
name="DynamicPathfindingDisabled"
persist="true"
@@ -8272,8 +8314,18 @@ Appearance has been saved to XML to [PATH]
<notification icon="notifytip.tga"
name="AppearanceToXMLFailed" type="notifytip">
Failed to save appearance to XML.
+ </notification>
+
+ <notification
+ icon="notifytip.tga"
+ name="PresetNotSaved"
+ type="notifytip">
+Error saving preset [NAME].
+ </notification>
+
+ <notification
icon="notifytip.tga"
- name="PresetNotDeleted"
+ name="PresetNotDeleted"
type="notifytip">
Error deleting preset [NAME].
</notification>
diff --git a/indra/newview/skins/default/xui/en/panel_login.xml b/indra/newview/skins/default/xui/en/panel_login.xml
index 183ae2e824..ae8e78a9d6 100644
--- a/indra/newview/skins/default/xui/en/panel_login.xml
+++ b/indra/newview/skins/default/xui/en/panel_login.xml
@@ -67,7 +67,7 @@
follows="left|top"
height="32"
left_pad="-11"
- max_length_bytes="16"
+ max_length_bytes="64"
text_pad_left="8"
name="password_edit"
label="Password"
diff --git a/indra/newview/skins/default/xui/en/panel_login_first.xml b/indra/newview/skins/default/xui/en/panel_login_first.xml
index d1416ece82..dc6e27a1ee 100644
--- a/indra/newview/skins/default/xui/en/panel_login_first.xml
+++ b/indra/newview/skins/default/xui/en/panel_login_first.xml
@@ -124,7 +124,7 @@
width="200"
height="32"
left="220"
- max_length_bytes="16"
+ max_length_bytes="64"
name="password_edit"
label="Password"
text_pad_left="8"
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml
index 3e96160834..4a5117adac 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml
@@ -138,7 +138,7 @@
initial_value="1"
layout="topleft"
left_pad="0"
- max_val="1.5"
+ max_val="2.0"
min_val="0.75"
name="ui_scale_slider"
top_pad="-14"
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index ae63546082..b19c6756bc 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -54,6 +54,7 @@ LLCEFLib/CEF Version: [LLCEFLIB_VERSION]
Voice Server Version: [VOICE_VERSION]
</string>
<string name="AboutTraffic">Packets Lost: [PACKETS_LOST,number,0]/[PACKETS_IN,number,0] ([PACKETS_PCT,number,1]%)</string>
+ <string name="AboutTime">[month, datetime, slt] [day, datetime, slt] [year, datetime, slt] [hour, datetime, slt]:[min, datetime, slt]:[second,datetime,slt]</string>
<string name="ErrorFetchingServerReleaseNotesURL">Error fetching server release notes URL.</string>
<string name="BuildConfiguration">Build Configuration</string>
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 1c77cf805e..3572b7dba8 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -27,9 +27,11 @@ Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
import sys
+import os
import os.path
import shutil
import errno
+import json
import re
import tarfile
import time
@@ -181,9 +183,16 @@ class ViewerManifest(LLManifest):
self.path("*.tga")
self.end_prefix("local_assets")
- # Files in the newview/ directory
+ # File in the newview/ directory
self.path("gpu_table.txt")
- # The summary.json file gets left in the build directory by newview/CMakeLists.txt.
+
+ #summary.json. Standard with exception handling is fine. If we can't open a new file for writing, we have worse problems
+ summary_dict = {"Type":"viewer","Version":'.'.join(self.args['version']),"Channel":self.channel_with_pkg_suffix()}
+ with open(os.path.join(os.pardir,'summary.json'), 'w') as summary_handle:
+ json.dump(summary_dict,summary_handle)
+
+ #we likely no longer need the test, since we will throw an exception above, but belt and suspenders and we get the
+ #return code for free.
if not self.path2basename(os.pardir, "summary.json"):
print "No summary.json file"
@@ -339,13 +348,15 @@ class Windows_i686_Manifest(ViewerManifest):
if self.is_packaging_viewer():
# Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe.
self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe())
+ # include the compiled launcher script so that it gets included in the file_list
+ self.path(src='%s/SL_Launcher.exe' % self.args['configuration'], dst="SL_Launcher.exe")
# Plugin host application
self.path2basename(os.path.join(os.pardir,
'llplugin', 'slplugin', self.args['configuration']),
"slplugin.exe")
- self.path2basename("../viewer_components/updater/scripts/windows", "update_install.bat")
+ #note, launcher and friends do not need viewer_manifest in Windows as the scripts are compiled into executables
# Get shared libs from the shared libs staging directory
if self.prefix(src=os.path.join(os.pardir, 'sharedlibs', self.args['configuration']),
dst=""):
@@ -610,7 +621,7 @@ class Windows_i686_Manifest(ViewerManifest):
substitution_strings['installer_file'] = installer_file
version_vars = """
- !define INSTEXE "%(final_exe)s"
+ !define INSTEXE "SL_Launcher.exe"
!define VERSION "%(version_short)s"
!define VERSION_LONG "%(version)s"
!define VERSION_DASHES "%(version_dashes)s"
@@ -692,6 +703,7 @@ class Darwin_i386_Manifest(ViewerManifest):
pkgdir = os.path.join(self.args['build'], os.pardir, 'packages')
relpkgdir = os.path.join(pkgdir, "lib", "release")
debpkgdir = os.path.join(pkgdir, "lib", "debug")
+ llbasedir = os.path.join(pkgdir, os.pardir)
if self.prefix(src="", dst="Contents"): # everything goes in Contents
self.path("Info.plist", dst="Info.plist")
@@ -702,7 +714,17 @@ class Darwin_i386_Manifest(ViewerManifest):
if self.prefix(dst="MacOS"):
self.path2basename("../viewer_components/updater/scripts/darwin", "*.py")
- self.end_prefix()
+ #this copies over the python wrapper script, associated utilities and required libraries, see SL-321, SL-322 and SL-323
+ self.path2basename("../viewer_components/manager","SL_Launcher")
+ self.path2basename("../viewer_components/manager","*.py")
+ llbase_path = os.path.join(self.get_dst_prefix(),'llbase')
+ if not os.path.exists(llbase_path):
+ os.makedirs(llbase_path)
+ if self.prefix(dst="llbase"):
+ self.path2basename("../packages/llbase","*.py")
+ self.path2basename("../packages/llbase","_cllsd.so")
+ self.end_prefix()
+ self.end_prefix()
# most everything goes in the Resources directory
if self.prefix(src="", dst="Resources"):
@@ -883,12 +905,6 @@ class Darwin_i386_Manifest(ViewerManifest):
self.run_command('strip -S %(viewer_binary)r' %
{ 'viewer_binary' : self.dst_path_of('Contents/MacOS/Second Life')})
- def copy_finish(self):
- # Force executable permissions to be set for scripts
- # see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
- for script in 'Contents/MacOS/update_install.py',:
- self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
-
def package_finish(self):
global CHANNEL_VENDOR_BASE
# MBW -- If the mounted volume name changes, it breaks the .DS_Store's background image and icon positioning.
@@ -1060,7 +1076,16 @@ class LinuxManifest(ViewerManifest):
self.path("secondlife-bin","do-not-directly-run-secondlife-bin")
self.path("../linux_crash_logger/linux-crash-logger","linux-crash-logger.bin")
self.path2basename("../llplugin/slplugin", "SLPlugin")
- self.path2basename("../viewer_components/updater/scripts/linux", "update_install")
+ #this copies over the python wrapper script, associated utilities and required libraries, see SL-321, SL-322 and SL-323
+ self.path2basename("../viewer_components/manager","SL_Launcher")
+ self.path2basename("../viewer_components/manager","*.py")
+ llbase_path = os.path.join(self.get_dst_prefix(),'llbase')
+ if not os.path.exists(llbase_path):
+ os.makedirs(llbase_path)
+ if self.prefix(dst="llbase"):
+ self.path2basename("../packages/llbase","*.py")
+ self.path2basename("../packages/llbase","_cllsd.so")
+ self.end_prefix()
self.end_prefix("bin")
if self.prefix("res-sdl"):
@@ -1089,12 +1114,6 @@ class LinuxManifest(ViewerManifest):
self.path("featuretable_linux.txt")
- def copy_finish(self):
- # Force executable permissions to be set for scripts
- # see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
- for script in 'secondlife', 'bin/update_install':
- self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
-
def package_finish(self):
installer_name = self.installer_base_name()
diff --git a/indra/viewer_components/Resources/README b/indra/viewer_components/Resources/README
new file mode 100644
index 0000000000..e1b35730d4
--- /dev/null
+++ b/indra/viewer_components/Resources/README
@@ -0,0 +1,9 @@
+This directory only exists as a place for the summary.json file to exist when the unit tests are run on a Mac, where the file goes to a sibling directory of the scripts dir. In Linux and Windows, the JSON file goes into the same directory as the script.
+
+See:
+
+test_get_summary.py
+update_manager.get_summary()
+
+for more details
+- coyot 201606.02
diff --git a/indra/viewer_components/Resources/summary.json b/indra/viewer_components/Resources/summary.json
new file mode 100644
index 0000000000..b78859d427
--- /dev/null
+++ b/indra/viewer_components/Resources/summary.json
@@ -0,0 +1 @@
+{"Type":"viewer","Version":"4.0.5.315117","Channel":"Second Life Release"}
diff --git a/indra/viewer_components/manager/InstallerError.py b/indra/viewer_components/manager/InstallerError.py
new file mode 100644
index 0000000000..3b199ea231
--- /dev/null
+++ b/indra/viewer_components/manager/InstallerError.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""\
+@file InstallerError.py
+@author coyot
+@date 2016-05-16
+@brief custom exception class for VMP
+
+$LicenseInfo:firstyear=2016&license=viewerlgpl$
+Second Life Viewer Source Code
+Copyright (C) 2016, Linden Research, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation;
+version 2.1 of the License only.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+$/LicenseInfo$
+"""
+
+"""
+usage:
+
+>>> import InstallerError
+>>> import os
+>>> try:
+... os.mkdir('/tmp')
+... except OSError, oe:
+... ie = InstallerError.InstallerError(oe, "foo")
+... raise ie
+
+Traceback (most recent call last):
+ File "<stdin>", line 5, in <module>
+InstallerError.InstallerError: [Errno [Errno 17] File exists: '/tmp'] foo
+"""
+
+class InstallerError(OSError):
+ def __init___(self, message):
+ Exception.__init__(self, message)
diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py
new file mode 100644
index 0000000000..f66af81d06
--- /dev/null
+++ b/indra/viewer_components/manager/InstallerUserMessage.py
@@ -0,0 +1,299 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $LicenseInfo:firstyear=2013&license=viewerlgpl$
+# Copyright (c) 2013, Linden Research, Inc.
+# $/LicenseInfo$
+
+"""
+@file InstallerUserMessage.py
+@author coyot
+@date 2016-05-16
+"""
+
+"""
+This does everything the old updater/scripts/darwin/messageframe.py script did and some more bits.
+Pushed up the manager directory to be multiplatform.
+"""
+
+import os
+import Queue
+import threading
+import time
+import Tkinter as tk
+import ttk
+
+
+class InstallerUserMessage(tk.Tk):
+ #Goals for this class:
+ # Provide a uniform look and feel
+ # Provide an easy to use convenience class for other scripts
+ # Provide windows that automatically disappear when done (for differing notions of done)
+ # Provide a progress bar that isn't a glorified spinner, but based on download progress
+ #Non-goals:
+ # No claim to threadsafety is made or warranted. Your mileage may vary.
+ # Please consult a doctor if you experience thread pain.
+
+ #Linden standard green color, from Marketing
+ linden_green = "#487A7B"
+
+ def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None):
+ tk.Tk.__init__(self)
+ self.grid()
+ self.title(title)
+ self.choice = tk.BooleanVar()
+ self.config(background = 'black')
+ # background="..." doesn't work on MacOS for radiobuttons or progress bars
+ # http://tinyurl.com/tkmacbuttons
+ ttk.Style().configure('Linden.TLabel', foreground=InstallerUserMessage.linden_green, background='black')
+ ttk.Style().configure('Linden.TButton', foreground=InstallerUserMessage.linden_green, background='black')
+ ttk.Style().configure("black.Horizontal.TProgressbar", foreground=InstallerUserMessage.linden_green, background='black')
+
+ #This bit of configuration centers the window on the screen
+ # The constants below are to adjust for typical overhead from the
+ # frame borders.
+ self.xp = (self.winfo_screenwidth() / 2) - (width / 2) - 8
+ self.yp = (self.winfo_screenheight() / 2) - (height / 2) - 20
+ self.geometry('{0}x{1}+{2}+{3}'.format(width, height, self.xp, self.yp))
+
+ #find a few things
+ self.script_dir = os.path.dirname(os.path.realpath(__file__))
+ self.icon_dir = os.path.abspath(os.path.join(self.script_dir, 'icons'))
+
+ #finds the icon and creates the widget
+ self.find_icon(icon_path, icon_name)
+
+ #defines what to do when window is closed
+ self.protocol("WM_DELETE_WINDOW", self._delete_window)
+
+ def _delete_window(self):
+ #capture and discard all destroy events before the choice is set
+ if not ((self.choice == None) or (self.choice == "")):
+ try:
+ self.destroy()
+ except:
+ #tk may try to destroy the same object twice
+ pass
+
+ def set_colors(self, widget):
+ # #487A7B is "Linden Green"
+ widget.config(foreground = InstallerUserMessage.linden_green)
+ widget.config(background='black')
+
+ def find_icon(self, icon_path = None, icon_name = None):
+ #we do this in each message, let's do it just once instead.
+ if not icon_path:
+ icon_path = self.icon_dir
+ icon_path = os.path.join(icon_path, icon_name)
+ if os.path.exists(icon_path):
+ icon = tk.PhotoImage(file=icon_path)
+ self.image_label = tk.Label(image = icon)
+ self.image_label.image = icon
+ else:
+ #default to text if image not available
+ self.image_label = tk.Label(text = "Second Life")
+
+ def auto_resize(self, row_count = 0, column_count = 0, heavy_row = None, heavy_column = None):
+ #auto resize window to fit all rows and columns
+ #"heavy" gets extra weight
+ for x in range(column_count):
+ if x == heavy_column:
+ self.columnconfigure(x, weight = 2)
+ else:
+ self.columnconfigure(x, weight=1)
+
+ for y in range(row_count):
+ if y == heavy_row:
+ self.rowconfigure(y, weight = 2)
+ else:
+ self.rowconfigure(x, weight=1)
+
+ def basic_message(self, message):
+ #message: text to be displayed
+ #icon_path: directory holding the icon, defaults to icons subdir of script dir
+ #icon_name: filename of icon to be displayed
+ self.choice.set(True)
+ self.text_label = tk.Label(text = message)
+ self.set_colors(self.text_label)
+ self.set_colors(self.image_label)
+ #pad, direction and weight are all experimentally derived by retrying various values
+ self.image_label.grid(row = 1, column = 1, sticky = 'W')
+ self.text_label.grid(row = 1, column = 2, sticky = 'W', padx =100)
+ self.auto_resize(row_count = 1, column_count = 2)
+ self.mainloop()
+
+ def binary_choice_message(self, message, true = 'Yes', false = 'No'):
+ #true: first option, returns True
+ #false: second option, returns False
+ #usage is kind of opaque and relies on this object persisting after the window destruction to pass back choice
+ #usage:
+ # frame = InstallerUserMessage.InstallerUserMessage( ... )
+ # frame = frame.binary_choice_message( ... )
+ # (wait for user to click)
+ # value = frame.choice.get()
+
+ self.text_label = tk.Label(text = message)
+ #command registers the callback to the method named. We want the frame to go away once clicked.
+ #button 1 returns True/1, button 2 returns False/0
+ self.button_one = ttk.Radiobutton(text = true, variable = self.choice, value = True,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.button_two = ttk.Radiobutton(text = false, variable = self.choice, value = False,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.set_colors(self.text_label)
+ self.set_colors(self.image_label)
+ #pad, direction and weight are all experimentally derived by retrying various values
+ self.image_label.grid(row = 1, column = 1, rowspan = 3, sticky = 'W')
+ self.text_label.grid(row = 1, column = 2, rowspan = 3)
+ self.button_one.grid(row = 1, column = 3, sticky = 'W', pady = 40)
+ self.button_two.grid(row = 2, column = 3, sticky = 'W', pady = 0)
+ self.auto_resize(row_count = 2, column_count = 3, heavy_column = 3)
+ #self.button_two.deselect()
+ self.update()
+ self.mainloop()
+
+ def trinary_choice_message(self, message, one = 1, two = 2, three = 3):
+ #one: first option, returns 1
+ #two: second option, returns 2
+ #three: third option, returns 3
+ #usage is kind of opaque and relies on this object persisting after the window destruction to pass back choice
+ #usage:
+ # frame = InstallerUserMessage.InstallerUserMessage( ... )
+ # frame = frame.binary_choice_message( ... )
+ # (wait for user to click)
+ # value = frame.choice.get()
+
+ self.text_label = tk.Label(text = message)
+ #command registers the callback to the method named. We want the frame to go away once clicked.
+ self.button_one = ttk.Radiobutton(text = one, variable = self.choice, value = 1,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.button_two = ttk.Radiobutton(text = two, variable = self.choice, value = 2,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.button_three = ttk.Radiobutton(text = three, variable = self.choice, value = 3,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.set_colors(self.text_label)
+ self.set_colors(self.image_label)
+ #pad, direction and weight are all experimentally derived by retrying various values
+ self.image_label.grid(row = 1, column = 1, rowspan = 4, sticky = 'W')
+ self.text_label.grid(row = 1, column = 2, rowspan = 4, padx = 5)
+ self.button_one.grid(row = 1, column = 3, sticky = 'W', pady = 5)
+ self.button_two.grid(row = 2, column = 3, sticky = 'W', pady = 5)
+ self.button_three.grid(row = 3, column = 3, sticky = 'W', pady = 5)
+ self.auto_resize(row_count = 3, column_count = 3, heavy_column = 3)
+ #self.button_two.deselect()
+ self.update()
+ self.mainloop()
+
+ def progress_bar(self, message = None, size = 0, interval = 100, pb_queue = None):
+ #Best effort attempt at a real progress bar
+ # This is what Tk calls "determinate mode" rather than "indeterminate mode"
+ #size: denominator of percent complete
+ #interval: frequency, in ms, of how often to poll the file for progress
+ #pb_queue: queue object used to send updates to the bar
+ self.text_label = tk.Label(text = message)
+ self.set_colors(self.text_label)
+ self.set_colors(self.image_label)
+ self.image_label.grid(row = 1, column = 1, sticky = 'NSEW')
+ self.text_label.grid(row = 2, column = 1, sticky = 'NSEW')
+ self.progress = ttk.Progressbar(self, style = 'black.Horizontal.TProgressbar', orient="horizontal", length=100, mode="determinate")
+ self.progress.grid(row = 3, column = 1, sticky = 'NSEW')
+ self.value = 0
+ self.progress["maximum"] = size
+ self.auto_resize(row_count = 1, column_count = 3)
+ self.queue = pb_queue
+ self.check_scheduler()
+
+ def check_scheduler(self):
+ if self.value < self.progress["maximum"]:
+ self.check_queue()
+ self.after(100, self.check_scheduler)
+
+ def check_queue(self):
+ while self.queue.qsize():
+ try:
+ msg = float(self.queue.get(0))
+ #custom signal, time to tear down
+ if msg == -1:
+ self.choice.set(True)
+ self.destroy()
+ else:
+ self.progress.step(msg)
+ self.value = msg
+ except Queue.Empty:
+ #nothing to do
+ return
+
+class ThreadedClient(threading.Thread):
+ #for test only, not part of the functional code
+ def __init__(self, queue):
+ threading.Thread.__init__(self)
+ self.queue = queue
+
+ def run(self):
+ for x in range(1, 90, 10):
+ time.sleep(1)
+ print "run " + str(x)
+ self.queue.put(10)
+ #tkk progress bars wrap at exactly 100 percent, look full at 99%
+ print "leftovers"
+ self.queue.put(9)
+ time.sleep(5)
+ # -1 is a custom signal to the progress_bar to quit
+ self.queue.put(-1)
+
+if __name__ == "__main__":
+ #When run as a script, just test the InstallUserMessage.
+ #To proceed with the test, close the first window, select on the second. The third will close by itself.
+ import sys
+ import tempfile
+
+ def set_and_check(frame, value):
+ print "value: " + str(value)
+ frame.progress.step(value)
+ if frame.progress["value"] < frame.progress["maximum"]:
+ print "In Progress"
+ else:
+ print "Over now"
+
+ #basic message window test
+ frame2 = InstallerUserMessage(text = "Something in the way she moves....", title = "Beatles Quotes for 100", icon_name="head-sl-logo.gif")
+ frame2.basic_message(message = "...attracts me like no other.")
+ print "Destroyed!"
+ sys.stdout.flush()
+
+ #binary choice test. User destroys window when they select.
+ frame3 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 200", icon_name="head-sl-logo.gif")
+ frame3.binary_choice_message(message = "And all I have to do is think of her.",
+ true = "Don't want to leave her now", false = 'You know I believe and how')
+ print frame3.choice.get()
+ sys.stdout.flush()
+
+ #progress bar
+ queue = Queue.Queue()
+ thread = ThreadedClient(queue)
+ thread.start()
+ print "thread started"
+
+ frame4 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 300", icon_name="head-sl-logo.gif")
+ frame4.progress_bar(message = "You're asking me will my love grow", size = 100, pb_queue = queue)
+ print "frame defined"
+ frame4.mainloop()
+
+ #trinary choice test. User destroys window when they select.
+ frame3a = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 200", icon_name="head-sl-logo.gif")
+ frame3a.trinary_choice_message(message = "And all I have to do is think of her.",
+ one = "Don't want to leave her now", two = 'You know I believe and how', three = 'John is Dead')
+ print frame3a.choice.get()
+ sys.stdout.flush()
diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher
new file mode 100755
index 0000000000..0403e01cec
--- /dev/null
+++ b/indra/viewer_components/manager/SL_Launcher
@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+# Copyright (c) 2013, Linden Research, Inc.
+
+import os
+import sys
+
+#module globals
+log_file_handle = None
+cwd = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.join(cwd, 'llbase'))
+
+import argparse
+import collections
+import InstallerUserMessage
+#NOTA BENE:
+# For POSIX platforms, llsd.py will be imported from the same directory.
+# For Windows, llsd.py will be compiled into the executable by pyinstaller
+from llbase import llsd
+import platform
+import subprocess
+import update_manager
+
+
+def silent_write(log_file_handle, text):
+ #if we have a log file, write. If not, do nothing.
+ #this is so we don't have to keep trapping for an exception with a None handle
+ #oh and because it is best effort, it is also a holey_write ;)
+ if (log_file_handle):
+ #prepend text for easy grepping
+ log_file_handle.write("SL LAUNCHER: " + text + "\n")
+
+def get_cmd_line():
+ platform_name = platform.system()
+ #find the parent of the logs and user_settings directories
+ if (platform_name == 'Darwin'):
+ settings_file = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'Resources/app_settings/cmd_line.xml')
+ elif (platform_name == 'Linux'):
+ settings_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'app_settings/cmd_line.xml')
+ #using list format of join is important here because the Windows pathsep in a string escapes the next char
+ elif (platform_name == 'Windows'):
+ settings_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'app_settings/cmd_line.xml')
+ else:
+ settings_file = None
+
+ try:
+ cmd_line = llsd.parse((open(settings_file)).read())
+ except:
+ silent_write(log_file_handle, "Could not parse settings file %s" % settings_file)
+ cmd_line = None
+
+ return cmd_line
+
+def get_settings():
+ #return the settings file parsed into a dict
+ try:
+ settings_file = os.path.abspath(os.path.join(parent_dir,'user_settings','settings.xml'))
+ settings = llsd.parse((open(settings_file)).read())
+ except llsd.LLSDParseError as lpe:
+ silent_write(log_file_handle, "Could not parse settings file %s" % lpe)
+ return None
+ return settings
+
+def capture_vmp_args(arg_list = None, cmd_line = None):
+ #expected input format: arg_list = ['--set', 'foo', 'bar', '-X', '-Y', 'qux']
+ #take a copy of the viewer parameters that are of interest to VMP.
+ #the regex for a parameter is --<param> {opt1} {opt2}
+ cli_overrides = {}
+ cmd_line = get_cmd_line()
+
+ vmp_params = {'--channel':'channel', '--settings':'settings', '--update-service':'update-service', '--set':'set'}
+ #the settings set with --set. All such settings have only one argument.
+ vmp_setters = ('UpdaterMaximumBandwidth', 'UpdaterServiceCheckPeriod', 'UpdaterServicePath', 'UpdaterServiceSetting', 'UpdaterServiceURL', 'UpdaterWillingToTest')
+
+ #Here turn the list into a queue, popping off the left as we go. Note that deque() makes a copy by value, not by reference
+ #Because of the complexity introduced by the uncertainty of how many options a parameter can take, this is far less complicated code than the more
+ #pythonic (x,y) = <some generator> since we will sometimes have (x), sometimes (x,y) and sometimes (x,y,z)
+ #also, because the pop is destructive, we prevent ourselves from iterating back over list elements that iterator methods would peek ahead at
+ vmp_queue = collections.deque(arg_list)
+ while (len(vmp_queue)):
+ param = vmp_queue.popleft()
+ #if it is not one of ours, pop through args until we get to the next parameter
+ if param in vmp_params.keys():
+ if param == '--set':
+ setting_name = vmp_queue.popleft()
+ setting_value = vmp_queue.popleft()
+ if setting_name in vmp_setters:
+ cli_overrides[vmp_params[param]] = (setting_name, setting_value)
+ else:
+ #find out how many args this parameter has
+ no_dashes = vmp_params[param]
+ count = cmd_line[no_dashes]['count']
+ param_args = []
+ if count > 0:
+ for argh in range(0,count):
+ param_args.append(vmp_queue.popleft())
+ #the parameter name is the key, the (possibly empty) list of args is the value
+ cli_overrides[vmp_params[param]] = param_args
+ print "cli override param %s vmp_param %s args %s count %s" % (param, vmp_params[param], param_args, count)
+
+ #to prevent KeyErrors on missing keys, set the remainder to None
+ for key in vmp_params:
+ if key != '--set':
+ try:
+ cli_overrides[key]
+ except KeyError:
+ cli_overrides[key] = None
+ else:
+ cli_overrides["--set"] = {}
+ for arg in vmp_setters:
+ try:
+ cli_overrides[key][arg]
+ except KeyError:
+ cli_overrides[key][arg] = None
+ return cli_overrides
+
+#main entry point
+#this and a few other update manager methods really should be refactored into a util lib
+parent_dir = update_manager.get_parent_path(update_manager.get_platform_key())
+log_file_handle = update_manager.get_log_file_handle(parent_dir)
+
+executable_name = ""
+if sys.platform.startswith('darwin'):
+ executable_name = "Second Life"
+elif sys.platform.startswith("win") or sys.platform.startswith("cyg"):
+ if os.path.isfile(os.path.join(cwd,"SecondLifeViewer.exe")):
+ executable_name = "SecondLifeViewer.exe"
+ elif os.path.isfile(os.path.join(cwd,"SecondLifeTest.exe")):
+ executable_name = "SecondLifeTest.exe"
+ else:
+ sys.exit("Can't find Windows viewer binary")
+elif sys.platform.startswith("linux"):
+ executable_name = "secondlife"
+else:
+ #SL doesn't run on VMS or punch cards
+ sys.exit("Unsupported platform")
+
+#find the viewer to be lauched
+viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),executable_name)
+
+parser = argparse.ArgumentParser()
+args = parser.parse_known_args(sys.argv)
+#args[1] looks like ['./SL_Launcher', '--set', 'foo', 'bar', '-X', '-Y', 'qux'], dump the progname
+args_list_to_pass = args[1][1:]
+vmp_args = capture_vmp_args(args_list_to_pass)
+#make a copy by value, not by reference
+command = list(args_list_to_pass)
+
+(success, state, condition) = update_manager.update_manager(vmp_args)
+# From update_manager:
+# (False, 'setup', None): error occurred before we knew what the update was (e.g., in setup or parsing)
+# (False, 'download', version): we failed to download the new version
+# (False, 'apply', version): we failed to apply the new version
+# (True, None, None): No update found
+# (True, 'in place', True): update applied in place
+# (True, 'in place', path_to_new_launcher): Update applied by a new install to a new location
+# (True, 'background', True): background download initiated
+#These boil down three cases:
+# Success is False, then pop up a message and launch the current viewer
+# No update, update succeeded in place in foreground, or background update started: silently launch the current viewer channel
+# Updated succeed to a different channel, launch that viewer and exit
+if not success:
+ msg = 'Update failed in the %s process. Please check logs. Viewer will launch starting momentarily.' % state
+ update_manager.after_frame(msg)
+ command.insert(0,viewer_binary)
+ viewer_process = subprocess.Popen(command)
+ #at the moment, we just exit here. Later, the crash monitor will be launched at this point
+elif (success == True and
+ (state == None
+ or (state == 'background' and condition == True)
+ or (state == 'in_place' and condition == True))):
+ command.insert(0,viewer_binary)
+ viewer_process = subprocess.Popen(command)
+ #at the moment, we just exit here. Later, the crash monitor will be launched at this point
+else:
+ #'condition' is the path to the new launcher.
+ command.insert(0,condition)
+ viewer_process = subprocess.Popen(command)
+ sys.exit(0)
diff --git a/indra/viewer_components/manager/apply_update.py b/indra/viewer_components/manager/apply_update.py
new file mode 100755
index 0000000000..643e4ad2bc
--- /dev/null
+++ b/indra/viewer_components/manager/apply_update.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $LicenseInfo:firstyear=2016&license=viewerlgpl$
+# Copyright (c) 2016, Linden Research, Inc.
+# $/LicenseInfo$
+
+"""
+@file apply_update.py
+@author coyot
+@date 2016-06-28
+"""
+
+"""
+Applies an already downloaded update.
+"""
+
+import argparse
+import errno
+import fnmatch
+import InstallerUserMessage as IUM
+import os
+import os.path
+import plistlib
+import re
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+
+#Module level variables
+
+#fnmatch expressions
+LNX_REGEX = '*' + '.bz2'
+MAC_REGEX = '*' + '.dmg'
+MAC_APP_REGEX = '*' + '.app'
+WIN_REGEX = '*' + '.exe'
+
+#which install the updater is run from
+INSTALL_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
+#whether the update is to the INSTALL_DIR or not. Most of the time this is the case.
+IN_PLACE = True
+
+BUNDLE_IDENTIFIER = "com.secondlife.indra.viewer"
+# Magic OS directory name that causes Cocoa viewer to crash on OS X 10.7.5
+# (see MAINT-3331)
+STATE_DIR = os.path.join(os.environ["HOME"], "Library", "Saved Application State",
+ BUNDLE_IDENTIFIER + ".savedState")
+
+def silent_write(log_file_handle, text):
+ #if we have a log file, write. If not, do nothing.
+ if (log_file_handle):
+ #prepend text for easy grepping
+ log_file_handle.write("APPLY UPDATE: " + text + "\n")
+
+def get_filename(download_dir = None):
+ #given a directory that supposedly has the download, find the installable
+ #if you are on platform X and you give the updater a directory with an installable
+ #for platform Y, you are either trying something fancy or get what you deserve
+ #or both
+ for filename in os.listdir(download_dir):
+ if (fnmatch.fnmatch(filename, LNX_REGEX)
+ or fnmatch.fnmatch(filename, MAC_REGEX)
+ or fnmatch.fnmatch(filename, WIN_REGEX)):
+ return os.path.join(download_dir, filename)
+ #someone gave us a bad directory
+ return None
+
+def try_dismount(log_file_handle = None, installable = None, tmpdir = None):
+ #best effort cleanup try to dismount the dmg file if we have mounted one
+ #the French judge gave it a 5.8
+ try:
+ #use the df command to find the device name
+ #Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on
+ #/dev/disk1s2 2047936 643280 1404656 32% 80408 175582 31% /private/tmp/mnt/Second Life Installer
+ command = ["df", os.path.join(tmpdir, "Second Life Installer")]
+ output = subprocess.check_output(command)
+ #first word of second line of df output is the device name
+ mnt_dev = output.split('\n')[1].split()[0]
+ #do the dismount
+ command = ["hdiutil", "detach", "-force", mnt_dev]
+ output = subprocess.check_output(command)
+ silent_write(log_file_handle, "hdiutil detach succeeded")
+ silent_write(log_file_handle, output)
+ except Exception, e:
+ silent_write(log_file_handle, "Could not detach dmg file %s. Error messages: %s" % (installable, e.message))
+
+def apply_update(download_dir = None, platform_key = None, log_file_handle = None, in_place = True):
+ #for lnx and mac, returns path to newly installed viewer
+ #for win, return the name of the executable
+ #returns None on failure for all three
+ #throws an exception if it can't find an installable at all
+
+ IN_PLACE = in_place
+
+ installable = get_filename(download_dir)
+ if not installable:
+ #could not find the download
+ raise ValueError("Could not find installable in " + download_dir)
+
+ #apply update using the platform specific tools
+ if platform_key == 'lnx':
+ installed = apply_linux_update(installable, log_file_handle)
+ elif platform_key == 'mac':
+ installed = apply_mac_update(installable, log_file_handle)
+ elif platform_key == 'win':
+ installed = apply_windows_update(installable, log_file_handle)
+ else:
+ #wtf?
+ raise ValueError("Unknown Platform: " + platform_key)
+
+ if not installed:
+ #only mark the download as done when everything is done
+ done_filename = os.path.join(os.path.dirname(installable), ".done")
+ open(done_filename, 'w+').close()
+
+ return installed
+
+def apply_linux_update(installable = None, log_file_handle = None):
+ try:
+ #untar to tmpdir
+ tmpdir = tempfile.mkdtemp()
+ tar = tarfile.open(name = installable, mode="r:bz2")
+ tar.extractall(path = tmpdir)
+ if IN_PLACE:
+ #rename current install dir
+ shutil.move(INSTALL_DIR,install_dir + ".bak")
+ #mv new to current
+ shutil.move(tmpdir, INSTALL_DIR)
+ #delete tarball on success
+ os.remove(installable)
+ except Exception, e:
+ silent_write(log_file_handle, "Update failed due to " + repr(e))
+ return None
+ return INSTALL_DIR
+
+def apply_mac_update(installable = None, log_file_handle = None):
+ #INSTALL_DIR is something like /Applications/Second Life Viewer.app/Contents/MacOS, need to jump up two levels for the install base
+ install_base = os.path.dirname(INSTALL_DIR)
+ install_base = os.path.dirname(install_base)
+
+ #verify dmg file
+ try:
+ output = subprocess.check_output(["hdiutil", "verify", installable], stderr=subprocess.STDOUT)
+ silent_write(log_file_handle, "dmg verification succeeded")
+ silent_write(log_file_handle, output)
+ except Exception, e:
+ silent_write(log_file_handle, "Could not verify dmg file %s. Error messages: %s" % (installable, e.message))
+ return None
+ #make temp dir and mount & attach dmg
+ tmpdir = tempfile.mkdtemp()
+ try:
+ output = subprocess.check_output(["hdiutil", "attach", installable, "-mountroot", tmpdir])
+ silent_write(log_file_handle, "hdiutil attach succeeded")
+ silent_write(log_file_handle, output)
+ except Exception, e:
+ silent_write(log_file_handle, "Could not attach dmg file %s. Error messages: %s" % (installable, e.message))
+ return None
+ #verify plist
+ mounted_appdir = None
+ for top_dir in os.listdir(tmpdir):
+ for appdir in os.listdir(os.path.join(tmpdir, top_dir)):
+ appdir = os.path.join(os.path.join(tmpdir, top_dir), appdir)
+ if fnmatch.fnmatch(appdir, MAC_APP_REGEX):
+ try:
+ plist = os.path.join(appdir, "Contents", "Info.plist")
+ CFBundleIdentifier = plistlib.readPlist(plist)["CFBundleIdentifier"]
+ mounted_appdir = appdir
+ except:
+ #there is no except for this try because there are multiple directories that legimately don't have what we are looking for
+ pass
+ if not mounted_appdir:
+ silent_write(log_file_handle, "Could not find app bundle in dmg %s." % (installable,))
+ return None
+ if CFBundleIdentifier != BUNDLE_IDENTIFIER:
+ silent_write(log_file_handle, "Wrong or null bundle identifier for dmg %s. Bundle identifier: %s" % (installable, CFBundleIdentifier))
+ try_dismount(log_file_handle, installable, tmpdir)
+ return None
+ #do the install, finally
+ if IN_PLACE:
+ # swap out old install directory
+ bundlename = os.path.basename(mounted_appdir)
+ silent_write(log_file_handle, "Updating %s" % bundlename)
+ swapped_out = os.path.join(tmpdir, INSTALL_DIR.lstrip('/'))
+ shutil.move(install_base, swapped_out)
+ else:
+ silent_write(log_file_handle, "Installing %s" % install_base)
+
+ # copy over the new bits
+ try:
+ shutil.copytree(mounted_appdir, install_base, symlinks=True)
+ retcode = 0
+ except Exception, e:
+ # try to restore previous viewer
+ if os.path.exists(swapped_out):
+ silent_write(log_file_handle, "Install of %s failed, rolling back to previous viewer." % installable)
+ shutil.move(swapped_out, installed_test)
+ retcode = 1
+ finally:
+ try_dismount(log_file_handle, installable, tmpdir)
+ if retcode:
+ return None
+
+ #see MAINT-3331
+ try:
+ shutil.rmtree(STATE_DIR)
+ except Exception, e:
+ #if we fail to delete something that isn't there, that's okay
+ if e[0] == errno.ENOENT:
+ pass
+ else:
+ raise e
+
+ os.remove(installable)
+ return install_base
+
+def apply_windows_update(installable = None, log_file_handle = None):
+ #the windows install is just running the NSIS installer executable
+ #from VMP's perspective, it is a black box
+ try:
+ output = subprocess.check_output(installable, stderr=subprocess.STDOUT)
+ silent_write(log_file_handle, "Install of %s succeeded." % installable)
+ silent_write(log_file_handle, output)
+ except subprocess.CalledProcessError, cpe:
+ silent_write(log_file_handle, "%s failed with return code %s. Error messages: %s." %
+ (cpe.cmd, cpe.returncode, cpe.message))
+ return None
+ #Due to the black box nature of the install, we have to derive the application path from the
+ #name of the installable. This is essentially reverse-engineering app_name()/app_name_oneword()
+ #in viewer_manifest.py
+ #the format of the filename is: Second_Life_{Project Name}_A-B-C-XXXXXX_i686_Setup.exe
+ #which deploys to C:\Program Files (x86)\SecondLifeProjectName\
+ #so we want all but the last four phrases and tack on Viewer if there is no project
+ if re.search('Project', installable):
+ winstall = os.path.join("C:\\Program Files (x86)\\", "".join(installable.split("_")[:-3]))
+ else:
+ winstall = os.path.join("C:\\Program Files (x86)\\", "".join(installable.split("_")[:-3])+"Viewer")
+ return winstall
+
+def main():
+ parser = argparse.ArgumentParser("Apply Downloaded Update")
+ parser.add_argument('--dir', dest = 'download_dir', help = 'directory to find installable', required = True)
+ parser.add_argument('--pkey', dest = 'platform_key', help =' OS: lnx|mac|win', required = True)
+ parser.add_argument('--in_place', action = 'store_false', help = 'This upgrade is for a different channel', default = True)
+ parser.add_argument('--log_file', dest = 'log_file', default = None, help = 'file to write messages to')
+ args = parser.parse_args()
+
+ if args.log_file:
+ try:
+ f = open(args.log_file,'w+')
+ except:
+ print "%s could not be found or opened" % args.log_file
+ sys.exit(1)
+
+ IN_PLACE = args.in_place
+ result = apply_update(download_dir = args.download_dir, platform_key = args.platform_key, log_file_handle = f)
+ if not result:
+ sys.exit("Update failed")
+ else:
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/indra/viewer_components/manager/download_update.py b/indra/viewer_components/manager/download_update.py
new file mode 100755
index 0000000000..23f784c6c1
--- /dev/null
+++ b/indra/viewer_components/manager/download_update.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $LicenseInfo:firstyear=2016&license=viewerlgpl$
+# Copyright (c) 2016, Linden Research, Inc.
+# $/LicenseInfo$
+
+"""
+@file download_update.py
+@author coyot
+@date 2016-06-23
+"""
+
+"""
+Performs a download of an update. In a separate script from update_manager so that we can
+call it with subprocess.
+"""
+
+import argparse
+import InstallerUserMessage as IUM
+import os
+import Queue
+import requests
+import threading
+
+#module default
+CHUNK_SIZE = 1024
+
+def download_update(url = None, download_dir = None, size = None, progressbar = False, chunk_size = CHUNK_SIZE):
+ #url to download from
+ #download_dir to download to
+ #total size (for progressbar) of download
+ #progressbar: whether to display one (not used for background downloads)
+ #chunk_size is in bytes, amount to download at once
+
+ queue = Queue.Queue()
+ #the url split provides the basename of the filename
+ filename = os.path.join(download_dir, url.split('/')[-1])
+ req = requests.get(url, stream=True)
+ down_thread = ThreadedDownload(req, filename, chunk_size, progressbar, queue)
+ down_thread.start()
+
+ if progressbar:
+ frame = IUM.InstallerUserMessage(title = "Second Life Downloader", icon_name="head-sl-logo.gif")
+ frame.progress_bar(message = "Download Progress", size = size, pb_queue = queue)
+ frame.mainloop()
+ else:
+ #nothing for the main thread to do
+ down_thread.join()
+
+class ThreadedDownload(threading.Thread):
+ def __init__(self, req, filename, chunk_size, progressbar, in_queue):
+ #req is a python request object
+ #target filename to download to
+ #chunk_size is in bytes, amount to download at once
+ #progressbar: whether to display one (not used for background downloads)
+ #in_queue mediates communication between this thread and the progressbar
+ threading.Thread.__init__(self)
+ self.req = req
+ self.filename = filename
+ self.chunk_size = int(chunk_size)
+ self.progressbar = progressbar
+ self.in_queue = in_queue
+
+ def run(self):
+ with open(self.filename, 'wb') as fd:
+ #keep downloading until we run out of chunks, then download the last bit
+ for chunk in self.req.iter_content(self.chunk_size):
+ fd.write(chunk)
+ if self.progressbar:
+ #this will increment the progress bar by len(chunk)/size units
+ self.in_queue.put(len(chunk))
+ #signal value saying to the progress bar that it is done and can destroy itself
+ #if len(chunk) is ever -1, we get to file a bug against Python
+ self.in_queue.put(-1)
+
+def main():
+ #main method is for standalone use such as support and QA
+ #VMP will import this module and run download_update directly
+ parser = argparse.ArgumentParser("Download URI to directory")
+ parser.add_argument('--url', dest='url', help='URL of file to be downloaded', required=True)
+ parser.add_argument('--dir', dest='download_dir', help='directory to be downloaded to', required=True)
+ parser.add_argument('--pb', dest='progressbar', help='whether or not to show a progressbar', action="store_true", default = False)
+ parser.add_argument('--size', dest='size', help='size of download for progressbar')
+ parser.add_argument('--chunk_size', dest='chunk_size', default=CHUNK_SIZE, help='max portion size of download to be loaded in memory in bytes.')
+ args = parser.parse_args()
+
+ download_update(url = args.url, download_dir = args.download_dir, size = args.size, progressbar = args.progressbar, chunk_size = args.chunk_size)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/indra/viewer_components/manager/icons/SL_Logo.gif b/indra/viewer_components/manager/icons/SL_Logo.gif
new file mode 100644
index 0000000000..c24d6b08cb
--- /dev/null
+++ b/indra/viewer_components/manager/icons/SL_Logo.gif
Binary files differ
diff --git a/indra/viewer_components/manager/icons/SL_Logo.png b/indra/viewer_components/manager/icons/SL_Logo.png
new file mode 100644
index 0000000000..5e376c72f9
--- /dev/null
+++ b/indra/viewer_components/manager/icons/SL_Logo.png
Binary files differ
diff --git a/indra/viewer_components/manager/icons/head-sl-logo.gif b/indra/viewer_components/manager/icons/head-sl-logo.gif
new file mode 100644
index 0000000000..d635348dcc
--- /dev/null
+++ b/indra/viewer_components/manager/icons/head-sl-logo.gif
Binary files differ
diff --git a/indra/viewer_components/manager/icons/head-sl-logo.png b/indra/viewer_components/manager/icons/head-sl-logo.png
new file mode 100644
index 0000000000..5c214e96d1
--- /dev/null
+++ b/indra/viewer_components/manager/icons/head-sl-logo.png
Binary files differ
diff --git a/indra/viewer_components/manager/tests/data/settings.xml b/indra/viewer_components/manager/tests/data/settings.xml
new file mode 100644
index 0000000000..07e420dcb3
--- /dev/null
+++ b/indra/viewer_components/manager/tests/data/settings.xml
@@ -0,0 +1,1184 @@
+<llsd>
+ <map>
+ <key>AllowMultipleViewers</key>
+ <map>
+ <key>Comment</key>
+ <string>Allow multiple viewers.</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>AllowTapTapHoldRun</key>
+ <map>
+ <key>Comment</key>
+ <string>Tapping a direction key twice and holding it down makes avatar run</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>AppearanceCameraMovement</key>
+ <map>
+ <key>Comment</key>
+ <string>When entering appearance editing mode, camera zooms in on currently selected portion of avatar</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>AudioLevelMedia</key>
+ <map>
+ <key>Comment</key>
+ <string>Audio level of Quicktime movies</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.699999988079071044921875</real>
+ </map>
+ <key>AudioLevelMic</key>
+ <map>
+ <key>Comment</key>
+ <string>Audio level of microphone input</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.1749999970197677612304688</real>
+ </map>
+ <key>AudioLevelMusic</key>
+ <map>
+ <key>Comment</key>
+ <string>Audio level of streaming music</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0</real>
+ </map>
+ <key>AudioLevelSFX</key>
+ <map>
+ <key>Comment</key>
+ <string>Audio level of in-world sound effects</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.699999988079071044921875</real>
+ </map>
+ <key>AudioLevelVoice</key>
+ <map>
+ <key>Comment</key>
+ <string>Audio level of voice chat</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>1</real>
+ </map>
+ <key>AudioStreamingMedia</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable streaming</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>AvatarAxisDeadZone0</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 0 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.1000000014901161193847656</real>
+ </map>
+ <key>AvatarAxisDeadZone1</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 1 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.1000000014901161193847656</real>
+ </map>
+ <key>AvatarAxisDeadZone2</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 2 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.1000000014901161193847656</real>
+ </map>
+ <key>AvatarAxisDeadZone3</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 3 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>1</real>
+ </map>
+ <key>AvatarAxisDeadZone4</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 4 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.01999999955296516418457031</real>
+ </map>
+ <key>AvatarAxisDeadZone5</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 5 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>AvatarAxisScale3</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 3 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0</real>
+ </map>
+ <key>AvatarAxisScale4</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 4 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>2</real>
+ </map>
+ <key>AvatarAxisScale5</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar axis 5 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>2</real>
+ </map>
+ <key>AvatarFeathering</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar feathering (less is softer)</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>6</real>
+ </map>
+ <key>AvatarFileName</key>
+ <map>
+ <key>Comment</key>
+ <string>Alternative avatar file name</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>avatar_lad_tentacles.xml</string>
+ </map>
+ <key>BuildAxisDeadZone0</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 0 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>BuildAxisDeadZone1</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 1 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>BuildAxisDeadZone2</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 2 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>BuildAxisDeadZone3</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 3 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>BuildAxisDeadZone4</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 4 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>BuildAxisDeadZone5</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 5 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>BuildAxisScale0</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 0 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>6</real>
+ </map>
+ <key>BuildAxisScale1</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 1 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>6</real>
+ </map>
+ <key>BuildAxisScale2</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 2 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>6</real>
+ </map>
+ <key>BuildAxisScale3</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 3 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>6</real>
+ </map>
+ <key>BuildAxisScale4</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 4 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>6</real>
+ </map>
+ <key>BuildAxisScale5</key>
+ <map>
+ <key>Comment</key>
+ <string>Build axis 5 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>6</real>
+ </map>
+ <key>BuildFeathering</key>
+ <map>
+ <key>Comment</key>
+ <string>Build feathering (less is softer)</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>12</real>
+ </map>
+ <key>BulkChangeEveryoneCopy</key>
+ <map>
+ <key>Comment</key>
+ <string>Bulk changed objects can be copied by everyone</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>BulkChangeNextOwnerCopy</key>
+ <map>
+ <key>Comment</key>
+ <string>Bulk changed objects can be copied by next owner</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>BulkChangeNextOwnerModify</key>
+ <map>
+ <key>Comment</key>
+ <string>Bulk changed objects can be modified by next owner</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>BulkChangeShareWithGroup</key>
+ <map>
+ <key>Comment</key>
+ <string>Bulk changed objects are shared with the currently active group</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>CacheValidateCounter</key>
+ <map>
+ <key>Comment</key>
+ <string>Used to distribute cache validation</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>122</integer>
+ </map>
+ <key>CameraPosOnLogout</key>
+ <map>
+ <key>Comment</key>
+ <string>Camera position when last logged out (global coordinates)</string>
+ <key>Type</key>
+ <string>Vector3D</string>
+ <key>Value</key>
+ <array>
+ <real>288290.4477181434631347656</real>
+ <real>275988.5277819633483886719</real>
+ <real>49.10921102762222290039062</real>
+ </array>
+ </map>
+ <key>ClickToWalk</key>
+ <map>
+ <key>Comment</key>
+ <string>Click in world to walk to location</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>ConversationSortOrder</key>
+ <map>
+ <key>Comment</key>
+ <string>Specifies sort key for conversations</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>CurrentGrid</key>
+ <map>
+ <key>Comment</key>
+ <string>Currently Selected Grid</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>util.agni.lindenlab.com</string>
+ </map>
+ <key>Cursor3D</key>
+ <map>
+ <key>Comment</key>
+ <string>Treat Joystick values as absolute positions (not deltas).</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>FirstLoginThisInstall</key>
+ <map>
+ <key>Comment</key>
+ <string>Specifies that you have not logged in with the viewer since you performed a clean install</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>FirstRunThisInstall</key>
+ <map>
+ <key>Comment</key>
+ <string>Specifies that you have not run the viewer since you performed a clean install</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>FlycamAxisDeadZone0</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 0 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>FlycamAxisDeadZone1</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 1 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>FlycamAxisDeadZone2</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 2 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>FlycamAxisDeadZone3</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 3 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>FlycamAxisDeadZone4</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 4 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>FlycamAxisDeadZone5</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 5 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.009999999776482582092285156</real>
+ </map>
+ <key>FlycamAxisDeadZone6</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 6 dead zone.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>1</real>
+ </map>
+ <key>FlycamAxisScale0</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 0 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>42</real>
+ </map>
+ <key>FlycamAxisScale1</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 1 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>40</real>
+ </map>
+ <key>FlycamAxisScale2</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 2 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>40</real>
+ </map>
+ <key>FlycamAxisScale3</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 3 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0</real>
+ </map>
+ <key>FlycamAxisScale4</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 4 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>2</real>
+ </map>
+ <key>FlycamAxisScale5</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 5 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>3</real>
+ </map>
+ <key>FlycamAxisScale6</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam axis 6 scaler.</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0</real>
+ </map>
+ <key>FlycamFeathering</key>
+ <map>
+ <key>Comment</key>
+ <string>Flycam feathering (less is softer)</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>5</real>
+ </map>
+ <key>FocusPosOnLogout</key>
+ <map>
+ <key>Comment</key>
+ <string>Camera focus point when last logged out (global coordinates)</string>
+ <key>Type</key>
+ <string>Vector3D</string>
+ <key>Value</key>
+ <array>
+ <real>288287.8830481640761718154</real>
+ <real>275991.5973855691263452172</real>
+ <real>47.96361158013021963597566</real>
+ </array>
+ </map>
+ <key>ForceShowGrid</key>
+ <map>
+ <key>Comment</key>
+ <string>Always show grid dropdown on login screen</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>HttpProxyType</key>
+ <map>
+ <key>Comment</key>
+ <string>Proxy type to use for HTTP operations</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>None</string>
+ </map>
+ <key>JoystickInitialized</key>
+ <map>
+ <key>Comment</key>
+ <string>Whether or not a joystick has been detected and initiailized.</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>UnknownDevice</string>
+ </map>
+ <key>LSLFindCaseInsensitivity</key>
+ <map>
+ <key>Comment</key>
+ <string>Use case insensitivity when searching in LSL editor</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>LastFeatureVersion</key>
+ <map>
+ <key>Comment</key>
+ <string>[DO NOT MODIFY] Feature Table Version number for tracking rendering system changes</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>37</integer>
+ </map>
+ <key>LastGPUString</key>
+ <map>
+ <key>Comment</key>
+ <string>[DO NOT MODIFY] previous GPU id string for tracking hardware changes</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>NVIDIA Corporation NVIDIA GeForce GT 750M OpenGL Engine</string>
+ </map>
+ <key>LastPrefTab</key>
+ <map>
+ <key>Comment</key>
+ <string>Last selected tab in preferences window</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>LastRunVersion</key>
+ <map>
+ <key>Comment</key>
+ <string>Version number of last instance of the viewer that you ran</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>Second Life Project Bento 5.0.0.315657</string>
+ </map>
+ <key>LocalCacheVersion</key>
+ <map>
+ <key>Comment</key>
+ <string>Version number of cache</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>7</integer>
+ </map>
+ <key>LoginLocation</key>
+ <map>
+ <key>Comment</key>
+ <string>Default Login location (&apos;last&apos;, &apos;home&apos;) preference</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>home</string>
+ </map>
+ <key>MapScale</key>
+ <map>
+ <key>Comment</key>
+ <string>World map zoom level (pixels per region)</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>256</real>
+ </map>
+ <key>MaxJointsPerMeshObject</key>
+ <map>
+ <key>Comment</key>
+ <string>Maximum joints per rigged mesh object</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <real>51</real>
+ </map>
+ <key>MediaEnablePopups</key>
+ <map>
+ <key>Comment</key>
+ <string>If true, enable targeted links and javascript in media to open new media browser windows without a prompt.</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>MediaShowOnOthers</key>
+ <map>
+ <key>Comment</key>
+ <string>Whether or not to show media on other avatars</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>MigrateCacheDirectory</key>
+ <map>
+ <key>Comment</key>
+ <string>Check for old version of disk cache to migrate to current location</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>NavBarShowParcelProperties</key>
+ <map>
+ <key>Comment</key>
+ <string>Show parcel property icons in navigation bar</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>NextLoginLocation</key>
+ <map>
+ <key>Comment</key>
+ <string>Location to log into for this session - set from command line or the login panel, cleared following a successfull login.</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>home</string>
+ </map>
+ <key>NotificationConferenceIMOptions</key>
+ <map>
+ <key>Comment</key>
+ <string>
+ Specifies how the UI responds to Conference IM Notifications.
+ Allowed values: [openconversations,toast,flash,noaction]
+ </string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>none</string>
+ </map>
+ <key>NotificationFriendIMOptions</key>
+ <map>
+ <key>Comment</key>
+ <string>
+ Specifies how the UI responds to Friend IM Notifications.
+ Allowed values: [openconversations,toast,flash,noaction]
+ </string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>openconversations</string>
+ </map>
+ <key>NotificationGroupChatOptions</key>
+ <map>
+ <key>Comment</key>
+ <string>
+ Specifies how the UI responds to Group Chat Notifications.
+ Allowed values: [openconversations,toast,flash,noaction]
+ </string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>none</string>
+ </map>
+ <key>NotificationNearbyChatOptions</key>
+ <map>
+ <key>Comment</key>
+ <string>
+ Specifies how the UI responds to Nearby Chat Notifications.
+ Allowed values: [openconversations,toast,flash,noaction]
+ </string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>none</string>
+ </map>
+ <key>NotificationNonFriendIMOptions</key>
+ <map>
+ <key>Comment</key>
+ <string>
+ Specifies how the UI responds to Non Friend IM Notifications.
+ Allowed values: [openconversations,toast,flash,noaction]
+ </string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>openconversations</string>
+ </map>
+ <key>NumSessions</key>
+ <map>
+ <key>Comment</key>
+ <string>Number of successful logins to Second Life</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>1674</integer>
+ </map>
+ <key>PlayTypingAnim</key>
+ <map>
+ <key>Comment</key>
+ <string>Your avatar plays the typing animation whenever you type in the chat bar</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>PoolSizeExpCache</key>
+ <map>
+ <key>Comment</key>
+ <string>Coroutine Pool size for ExpCache</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>5</integer>
+ </map>
+ <key>PreferredBrowserBehavior</key>
+ <map>
+ <key>Comment</key>
+ <string>Use system browser for any links (0), use builtin browser for SL links and system one for others (1) or use builtin browser only (2).</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <string>0</string>
+ </map>
+ <key>PreferredMaturity</key>
+ <map>
+ <key>Comment</key>
+ <string>Setting for the user&apos;s preferred maturity level (consts in indra_constants.h)</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>42</integer>
+ </map>
+ <key>PresetGraphicActive</key>
+ <map>
+ <key>Comment</key>
+ <string>Name of currently selected preference</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>Default</string>
+ </map>
+ <key>ProbeHardwareOnStartup</key>
+ <map>
+ <key>Comment</key>
+ <string>Query current hardware configuration on application startup</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>QAMode</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable Testing Features.</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>RenderAnisotropic</key>
+ <map>
+ <key>Comment</key>
+ <string>Render textures using anisotropic filtering</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>RenderAvatarCloth</key>
+ <map>
+ <key>Comment</key>
+ <string>Controls if avatars use wavy cloth</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>RenderAvatarLODFactor</key>
+ <map>
+ <key>Comment</key>
+ <string>Controls level of detail of avatars (multiplier for current screen area when calculated level of detail)</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>1</real>
+ </map>
+ <key>RenderFSAASamples</key>
+ <map>
+ <key>Comment</key>
+ <string>Number of samples to use for FSAA (0 = no AA).</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>2</integer>
+ </map>
+ <key>RenderFarClip</key>
+ <map>
+ <key>Comment</key>
+ <string>Distance of far clip plane from camera (meters)</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>128</real>
+ </map>
+ <key>RenderMaxPartCount</key>
+ <map>
+ <key>Comment</key>
+ <string>Maximum number of particles to display on screen</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <real>2048</real>
+ </map>
+ <key>RenderQualityPerformance</key>
+ <map>
+ <key>Comment</key>
+ <string>Which graphics settings you&apos;ve chosen</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <real>4</real>
+ </map>
+ <key>RenderReflectionDetail</key>
+ <map>
+ <key>Comment</key>
+ <string>Detail of reflection render pass.</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>RenderTerrainLODFactor</key>
+ <map>
+ <key>Comment</key>
+ <string>Controls level of detail of terrain (multiplier for current screen area when calculated level of detail)</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>2</real>
+ </map>
+ <key>RenderVBOEnable</key>
+ <map>
+ <key>Comment</key>
+ <string>Use GL Vertex Buffer Objects</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>RenderVolumeLODFactor</key>
+ <map>
+ <key>Comment</key>
+ <string>Controls level of detail of primitives (multiplier for current screen area when calculated level of detail)</string>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>1.125</real>
+ </map>
+ <key>ShowAdvancedGraphicsSettings</key>
+ <map>
+ <key>Comment</key>
+ <string>Show advanced graphics settings</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>ShowBanLines</key>
+ <map>
+ <key>Comment</key>
+ <string>Show in-world ban/access borders</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>ShowStartLocation</key>
+ <map>
+ <key>Comment</key>
+ <string>Display starting location menu on login screen</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>SkeletonFileName</key>
+ <map>
+ <key>Comment</key>
+ <string>Alternative skeleton file name</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>avatar_skeleton_tentacles.xml</string>
+ </map>
+ <key>SkyPresetName</key>
+ <map>
+ <key>Comment</key>
+ <string>Sky preset to use. May be superseded by region settings or by a day cycle (see DayCycleName).</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>Sunset</string>
+ </map>
+ <key>SnapshotConfigURL</key>
+ <map>
+ <key>Comment</key>
+ <string>URL to fetch Snapshot Sharing configuration data from.</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>http://photos.apps.avatarsunited.com/viewer_config</string>
+ </map>
+ <key>SnapshotFormat</key>
+ <map>
+ <key>Comment</key>
+ <string>Save snapshots in this format (0 = PNG, 1 = JPEG, 2 = BMP)</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>SnapshotQuality</key>
+ <map>
+ <key>Comment</key>
+ <string>Quality setting of postcard JPEGs (0 = worst, 100 = best)</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>100</integer>
+ </map>
+ <key>SpellCheck</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable spellchecking on line and text editors</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>SpellCheckDictionary</key>
+ <map>
+ <key>Comment</key>
+ <string>Current primary and secondary dictionaries used for spell checking</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>English (United States)</string>
+ </map>
+ <key>TextureMemory</key>
+ <map>
+ <key>Comment</key>
+ <string>Amount of memory to use for textures in MB (0 = autodetect)</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>256</integer>
+ </map>
+ <key>UseDayCycle</key>
+ <map>
+ <key>Comment</key>
+ <string>Whether to use use a day cycle or a fixed sky.</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>UseDebugMenus</key>
+ <map>
+ <key>Comment</key>
+ <string>Turns on &quot;Debug&quot; menu</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>UseEnvironmentFromRegion</key>
+ <map>
+ <key>Comment</key>
+ <string>Choose whether to use the region&apos;s environment settings, or override them with the local settings.</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>VFSOldSize</key>
+ <map>
+ <key>Comment</key>
+ <string>[DO NOT MODIFY] Controls resizing of local file cache</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>102</integer>
+ </map>
+ <key>VFSSalt</key>
+ <map>
+ <key>Comment</key>
+ <string>[DO NOT MODIFY] Controls local file caching behavior</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>260093998</integer>
+ </map>
+ <key>VersionChannelName</key>
+ <map>
+ <key>Comment</key>
+ <string>Version information generated by running the viewer</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>Second Life Release</string>
+ </map>
+ <key>VertexShaderEnable</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable/disable all GLSL shaders (debug)</string>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>VoiceInputAudioDevice</key>
+ <map>
+ <key>Comment</key>
+ <string>Audio input device to use for voice</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>C-Media USB Audio Device</string>
+ </map>
+ <key>VoiceOutputAudioDevice</key>
+ <map>
+ <key>Comment</key>
+ <string>Audio output device to use for voice</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>C-Media USB Audio Device</string>
+ </map>
+ <key>WLSkyDetail</key>
+ <map>
+ <key>Comment</key>
+ <string>Controls vertex detail on the WindLight sky. Lower numbers will give better performance and uglier skies.</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>48</integer>
+ </map>
+ <key>WebProfileFloaterRect</key>
+ <map>
+ <key>Comment</key>
+ <string>Web profile floater dimensions</string>
+ <key>Type</key>
+ <string>Rect</string>
+ <key>Value</key>
+ <array>
+ <integer>1189</integer>
+ <integer>957</integer>
+ <integer>1674</integer>
+ <integer>277</integer>
+ </array>
+ </map>
+ <key>WindowHeight</key>
+ <map>
+ <key>Comment</key>
+ <string>SL viewer window height</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>1000</integer>
+ </map>
+ <key>WindowWidth</key>
+ <map>
+ <key>Comment</key>
+ <string>SL viewer window width</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>1626</integer>
+ </map>
+ <key>WindowX</key>
+ <map>
+ <key>Comment</key>
+ <string>X coordinate of upper left corner of SL viewer window, relative to upper left corner of primary display (pixels)</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>50</integer>
+ </map>
+ <key>WindowY</key>
+ <map>
+ <key>Comment</key>
+ <string>Y coordinate of upper left corner of SL viewer window, relative to upper left corner of primary display (pixels)</string>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>50</integer>
+ </map>
+ </map>
+</llsd>
diff --git a/indra/viewer_components/manager/tests/summary.json b/indra/viewer_components/manager/tests/summary.json
new file mode 100644
index 0000000000..b78859d427
--- /dev/null
+++ b/indra/viewer_components/manager/tests/summary.json
@@ -0,0 +1 @@
+{"Type":"viewer","Version":"4.0.5.315117","Channel":"Second Life Release"}
diff --git a/indra/viewer_components/manager/tests/test_InstallerError.py b/indra/viewer_components/manager/tests/test_InstallerError.py
new file mode 100644
index 0000000000..d722208b7f
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_InstallerError.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_InstallerError.py
+@author coyot
+@date 2016-06-01
+"""
+
+from nose.tools import assert_equal
+
+import InstallerError
+import os
+
+def test_InstallerError():
+ try:
+ #try to make our own homedir, this will fail on all three platforms
+ homedir = os.path.abspath(os.path.expanduser('~'))
+ os.mkdir(homedir)
+ except OSError, oe:
+ ie = InstallerError.InstallerError(oe, "Installer failed to create a homedir that already exists.")
+
+ assert_equal( str(ie),
+ "[Errno [Errno 17] File exists: '%s'] Installer failed to create a homedir that already exists." % homedir)
diff --git a/indra/viewer_components/manager/tests/test_check_for_completed_download.py b/indra/viewer_components/manager/tests/test_check_for_completed_download.py
new file mode 100644
index 0000000000..388bc900e9
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_check_for_completed_download.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_check_for_completed_download.py
+@author coyot
+@date 2016-06-03
+"""
+
+from nose.tools import *
+from nose import with_setup
+
+import os
+import shutil
+import tempfile
+import update_manager
+import with_setup_args
+
+def check_for_completed_download_setup():
+ tmpdir1 = tempfile.mkdtemp(prefix = 'test1')
+ tmpdir2 = tempfile.mkdtemp(prefix = 'test2')
+ tempfile.mkstemp(suffix = '.done', dir = tmpdir1)
+
+ return [tmpdir1,tmpdir2], {}
+
+def check_for_completed_download_teardown(tmpdir1,tmpdir2):
+ shutil.rmtree(tmpdir1, ignore_errors = True)
+ shutil.rmtree(tmpdir2, ignore_errors = True)
+
+@with_setup_args.with_setup_args(check_for_completed_download_setup, check_for_completed_download_teardown)
+def test_completed_check_for_completed_download(tmpdir1,tmpdir2):
+ assert_equal(update_manager.check_for_completed_download(tmpdir1), 'done'), "Failed to find completion marker"
+
+@with_setup_args.with_setup_args(check_for_completed_download_setup, check_for_completed_download_teardown)
+def test_incomplete_check_for_completed_download(tmpdir1,tmpdir2):
+ #should return False
+ incomplete = not update_manager.check_for_completed_download(tmpdir2)
+ assert incomplete, "False positive, should not mark complete without a marker" \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/test_convert_version_file_style.py b/indra/viewer_components/manager/tests/test_convert_version_file_style.py
new file mode 100644
index 0000000000..d700f91b84
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_convert_version_file_style.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_convert_version_file_style.py
+@author coyot
+@date 2016-06-01
+"""
+
+from nose.tools import assert_equal
+
+import update_manager
+
+def test_normal_form():
+ version = '1.2.3.456789'
+ golden = '1_2_3_456789'
+ converted = update_manager.convert_version_file_style(version)
+
+ assert_equal(golden, converted)
+
+def test_short_form():
+ version = '1.23'
+ golden = '1_23'
+ converted = update_manager.convert_version_file_style(version)
+
+ assert_equal(golden, converted)
+
+def test_idempotent():
+ version = '123'
+ golden = '123'
+ converted = update_manager.convert_version_file_style(version)
+
+ assert_equal(golden, converted)
+
+def test_none():
+ version = None
+ golden = None
+ converted = update_manager.convert_version_file_style(version)
+
+ assert_equal(golden, converted) \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/test_get_filename.py b/indra/viewer_components/manager/tests/test_get_filename.py
new file mode 100644
index 0000000000..95771d75dc
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_get_filename.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_get_filename.py
+@author coyot
+@date 2016-06-30
+"""
+
+from nose.tools import *
+from nose import with_setup
+
+import os
+import shutil
+import tempfile
+import apply_update
+import with_setup_args
+
+def get_filename_setup():
+ tmpdir1 = tempfile.mkdtemp(prefix = 'lnx')
+ tmpdir2 = tempfile.mkdtemp(prefix = 'mac')
+ tmpdir3 = tempfile.mkdtemp(prefix = 'win')
+ tmpdir4 = tempfile.mkdtemp(prefix = 'bad')
+ tempfile.mkstemp(suffix = '.bz2', dir = tmpdir1)
+ tempfile.mkstemp(suffix = '.dmg', dir = tmpdir2)
+ tempfile.mkstemp(suffix = '.exe', dir = tmpdir3)
+
+ return [tmpdir1,tmpdir2, tmpdir3, tmpdir4], {}
+
+def get_filename_teardown(tmpdir1,tmpdir2, tmpdir3, tmpdir4):
+ shutil.rmtree(tmpdir1, ignore_errors = True)
+ shutil.rmtree(tmpdir2, ignore_errors = True)
+ shutil.rmtree(tmpdir3, ignore_errors = True)
+ shutil.rmtree(tmpdir4, ignore_errors = True)
+
+@with_setup_args.with_setup_args(get_filename_setup, get_filename_teardown)
+def test_get_filename(tmpdir1, tmpdir2, tmpdir3, tmpdir4):
+ assert_is_not_none(apply_update.get_filename(tmpdir1)), "Failed to find installable"
+ assert_is_not_none(apply_update.get_filename(tmpdir2)), "Failed to find installable"
+ assert_is_not_none(apply_update.get_filename(tmpdir3)), "Failed to find installable"
+
+@with_setup_args.with_setup_args(get_filename_setup, get_filename_teardown)
+def test_missing_get_filename(tmpdir1, tmpdir2, tmpdir3, tmpdir4):
+ not_found = not apply_update.get_filename(tmpdir4)
+ assert not_found, "False positive, should not find an installable in an empty dir" \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/test_get_log_file_handle.py b/indra/viewer_components/manager/tests/test_get_log_file_handle.py
new file mode 100644
index 0000000000..c5b3c89550
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_get_log_file_handle.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_get_log_file_handle.py
+@author coyot
+@date 2016-06-08
+"""
+
+from nose.tools import *
+
+import os
+import shutil
+import tempfile
+import update_manager
+import with_setup_args
+
+def get_log_file_handle_setup():
+ tmpdir1 = tempfile.mkdtemp(prefix = 'test1')
+ tmpdir2 = tempfile.mkdtemp(prefix = 'test2')
+ log_file_path = os.path.abspath(os.path.join(tmpdir1,"update_manager.log"))
+ #not using tempfile because we want a particular filename
+ open(log_file_path, 'w+').close
+
+ return [tmpdir1,tmpdir2,log_file_path], {}
+
+def get_log_file_handle_teardown(tmpdir1,tmpdir2,log_file_path):
+ shutil.rmtree(tmpdir1, ignore_errors = True)
+ shutil.rmtree(tmpdir2, ignore_errors = True)
+
+@with_setup_args.with_setup_args(get_log_file_handle_setup, get_log_file_handle_teardown)
+def test_existing_get_log_file_handle(tmpdir1,tmpdir2,log_file_path):
+ handle = update_manager.get_log_file_handle(tmpdir1)
+ if not handle:
+ print "Failed to find existing log file"
+ assert False
+ elif not os.path.exists(os.path.abspath(log_file_path+".old")):
+ print "Failed to rotate update manager log"
+ assert False
+ assert True
+
+@with_setup_args.with_setup_args(get_log_file_handle_setup, get_log_file_handle_teardown)
+def test_missing_get_log_file_handle(tmpdir1,tmpdir2,log_file_path):
+ handle = update_manager.get_log_file_handle(tmpdir2)
+ if not os.path.exists(log_file_path):
+ print "Failed to touch new log file"
+ assert False
+ assert True \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/test_get_parent_path.py b/indra/viewer_components/manager/tests/test_get_parent_path.py
new file mode 100644
index 0000000000..3cfd72310e
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_get_parent_path.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_get_parent_path.py
+@author coyot
+@date 2016-06-02
+"""
+
+from nose.tools import *
+from nose import with_setup
+
+import os
+import shutil
+import update_manager
+import with_setup_args
+
+def get_parent_path_setup():
+ key = update_manager.get_platform_key()
+ try:
+ if key == 'mac':
+ settings_dir = os.path.join(os.path.expanduser('~'),'Library','Application Support','SecondLife')
+ elif key == 'lnx':
+ settings_dir = os.path.join(os.path.expanduser('~'),'.secondlife')
+ elif key == 'win':
+ settings_dir = os.path.join(os.path.expanduser('~'),'AppData','Roaming','SecondLife')
+ else:
+ raise Exception("Invalid Platform Key")
+
+ #preserve existing settings dir if any
+ if os.path.exists(settings_dir):
+ old_dir = settings_dir + ".tmp"
+ if os.path.exists(old_dir):
+ shutil.rmtree(old_dir, ignore_errors = True)
+ os.rename(settings_dir, old_dir)
+ os.makedirs(settings_dir)
+ except Exception, e:
+ print "get_parent_path_setup failed due to: %s" % str(e)
+ assert False
+
+ #this is we don't have to rediscover settings_dir for test and teardown
+ return [settings_dir], {}
+
+def get_parent_path_teardown(settings_dir):
+ try:
+ shutil.rmtree(settings_dir, ignore_errors = True)
+ #restore previous settings dir if any
+ old_dir = settings_dir + ".tmp"
+ if os.path.exists(old_dir):
+ os.rename(old_dir, settings_dir)
+ except:
+ #cleanup is best effort
+ pass
+
+@with_setup_args.with_setup_args(get_parent_path_setup, get_parent_path_teardown)
+def test_get_parent_path(settings_dir):
+ key = update_manager.get_platform_key()
+ got_settings_dir = update_manager.get_parent_path(key)
+
+ assert settings_dir, "test_get_parent_path failed to obtain parent path"
+
+ assert_equal(settings_dir, got_settings_dir)
+
+ \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/test_get_platform_key.py b/indra/viewer_components/manager/tests/test_get_platform_key.py
new file mode 100644
index 0000000000..37c570532c
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_get_platform_key.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_get_platform_key.py
+@author coyot
+@date 2016-06-01
+"""
+
+from nose.tools import assert_equal
+
+import platform
+import update_manager
+
+def test_get_platform_key():
+ key = update_manager.get_platform_key()
+ if key == 'mac':
+ assert_equal(platform.system(),'Darwin')
+ elif key == 'lnx':
+ assert_equal(platform.system(),'Linux')
+ elif key == 'win':
+ assert_equal(platform.system(),'Windows')
+ else:
+ assert_equal(key, None)
+ \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/test_get_settings.py b/indra/viewer_components/manager/tests/test_get_settings.py
new file mode 100644
index 0000000000..46b779cbd5
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_get_settings.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_get_settings.py
+@author coyot
+@date 2016-06-03
+"""
+
+from nose.tools import *
+from nose import with_setup
+
+import os
+import shutil
+import update_manager
+import with_setup_args
+
+def get_settings_setup():
+ try:
+ key = update_manager.get_platform_key()
+ settings_dir = os.path.join(update_manager.get_parent_path(key), "user_settings")
+ print settings_dir
+
+ #preserve existing settings dir if any
+ if os.path.exists(settings_dir):
+ old_dir = settings_dir + ".tmp"
+ if os.path.exists(old_dir):
+ shutil.rmtree(old_dir, ignore_errors = True)
+ os.rename(settings_dir, old_dir)
+ os.makedirs(settings_dir)
+
+ #the data subdir of the tests dir that this script is in
+ data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data")
+ #the test settings file
+ settings_file = os.path.join(data_dir, "settings.xml")
+ shutil.copyfile(settings_file, os.path.join(settings_dir, "settings.xml"))
+
+ except Exception, e:
+ print "get_settings_setup failed due to: %s" % str(e)
+ assert False
+
+ #this is we don't have to rediscover settings_dir for test and teardown
+ return [settings_dir], {}
+
+def get_settings_teardown(settings_dir):
+ try:
+ shutil.rmtree(settings_dir, ignore_errors = True)
+ #restore previous settings dir if any
+ old_dir = settings_dir + ".tmp"
+ if os.path.exists(old_dir):
+ os.rename(old_dir, settings_dir)
+ except:
+ #cleanup is best effort
+ pass
+
+@with_setup_args.with_setup_args(get_settings_setup, get_settings_teardown)
+def test_get_settings(settings_dir):
+ key = update_manager.get_platform_key()
+ parent = update_manager.get_parent_path(key)
+ log_file = update_manager.get_log_file_handle(parent)
+ got_settings = update_manager.get_settings(log_file, parent)
+
+ assert got_settings, "test_get_settings failed to find a settings.xml file"
+
+ #test one key just to make sure it parsed
+ assert_equal(got_settings['CurrentGrid']['Value'], 'util.agni.lindenlab.com')
+
diff --git a/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py b/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py
new file mode 100644
index 0000000000..513502a6ca
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_make_VVM_UUID_hash.py
+@author coyot
+@date 2016-06-03
+"""
+
+from nose.tools import *
+
+import update_manager
+
+def test_make_VVM_UUID_hash():
+ #because the method returns different results on different hosts
+ #it is not easy to unit test it reliably.
+ #About the best we can do is check for the exception from subprocess
+ key = update_manager.get_platform_key()
+ try:
+ UUID_hash = update_manager.make_VVM_UUID_hash(key)
+ except Exception, e:
+ print "Test failed due to: %s" % str(e)
+ assert False
+
+ #make_UUID_hash returned None
+ assert UUID_hash, "make_UUID_hash failed to make a hash."
+
diff --git a/indra/viewer_components/manager/tests/test_make_download_dir.py b/indra/viewer_components/manager/tests/test_make_download_dir.py
new file mode 100644
index 0000000000..5198a6de05
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_make_download_dir.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_make_download_dir.py
+@author coyot
+@date 2016-06-03
+"""
+
+from nose.tools import *
+
+import update_manager
+
+def test_make_download_dir():
+ key = update_manager.get_platform_key()
+ path = update_manager.get_parent_path(key)
+ version = '1.2.3.456789'
+ try:
+ download_dir = update_manager.make_download_dir(path, version)
+ except OSError, e:
+ print "make_download_dir failed to eat OSError %s" % str(e)
+ assert False
+ except Exception, e:
+ print "make_download_dir raised an unexpected exception %s" % str(e)
+ assert False
+
+ assert download_dir, "make_download_dir returned None for path %s and version %s" % (path, version) \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/test_query_vvm.py b/indra/viewer_components/manager/tests/test_query_vvm.py
new file mode 100644
index 0000000000..40a0f7b215
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_query_vvm.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_query_vvm.py
+@author coyot
+@date 2016-06-08
+"""
+
+from nose.tools import *
+
+import os
+import re
+import shutil
+import tempfile
+import update_manager
+import with_setup_args
+
+def query_vvm_setup():
+ tmpdir1 = tempfile.mkdtemp(prefix = 'test1')
+ handle = update_manager.get_log_file_handle(tmpdir1)
+
+ return [tmpdir1,handle], {}
+
+def query_vvm_teardown(tmpdir1, handle):
+ shutil.rmtree(tmpdir1, ignore_errors = True)
+
+@with_setup_args.with_setup_args(query_vvm_setup, query_vvm_teardown)
+def test_query_vvm(tmpdir1, handle):
+ key = update_manager.get_platform_key()
+ parent = update_manager.get_parent_path(key)
+ settings = update_manager.get_settings(handle, parent)
+ launcher_path = os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))
+ summary = update_manager.get_summary(key, launcher_path)
+
+ #for unit testing purposes, just testing a value from results. If no update, then None and it falls through
+ #for formal QA see:
+ # https://docs.google.com/document/d/1WNjOPdKlq0j_7s7gdNe_3QlyGnQDa3bFNvtyVM6Hx8M/edit
+ # https://wiki.lindenlab.com/wiki/Login_Test#Test_Viewer_Updater
+ #for test plans on all cases, as it requires setting up a fake VVM service
+
+ try:
+ results = update_manager.query_vvm(handle, key, settings, summary)
+ except Exception, e:
+ print "query_vvm threw unexpected exception %s" % str(e)
+ assert False
+
+ if results:
+ pattern = re.compile('Second Life')
+ assert pattern.search(results['channel']), "Bad results returned %s" % str(results)
+
+ assert True
diff --git a/indra/viewer_components/manager/tests/test_silent_write.py b/indra/viewer_components/manager/tests/test_silent_write.py
new file mode 100644
index 0000000000..a55665799f
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_silent_write.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_silent_write.py
+@author coyot
+@date 2016-06-02
+"""
+
+from nose.tools import *
+
+import tempfile
+import update_manager
+
+def test_silent_write_to_file():
+ test_log = tempfile.TemporaryFile()
+ try:
+ update_manager.silent_write(test_log, "This is a test.")
+ except Exception, e:
+ print "Test failed due to: %s" % str(e)
+ assert False
+
+def test_silent_write_to_null():
+ try:
+ update_manager.silent_write(None, "This is a test.")
+ except Exception, e:
+ print "Test failed due to: %s" % str(e)
+ assert False \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/test_summary.py b/indra/viewer_components/manager/tests/test_summary.py
new file mode 100644
index 0000000000..b318012b54
--- /dev/null
+++ b/indra/viewer_components/manager/tests/test_summary.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+
+"""
+@file test_summary.py
+@author coyot
+@date 2016-06-02
+"""
+
+from nose.tools import *
+
+import os.path
+import tempfile
+import update_manager
+
+def test_get_summary():
+ key = update_manager.get_platform_key()
+ #launcher is one dir above tests
+ launcher_path = os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))
+ summary_json = update_manager.get_summary(key, launcher_path)
+
+ #we aren't testing the JSON library, one key pair is enough
+ #so we will use the one pair that is actually a constant
+ assert_equal(summary_json['Type'],'viewer') \ No newline at end of file
diff --git a/indra/viewer_components/manager/tests/with_setup_args.py b/indra/viewer_components/manager/tests/with_setup_args.py
new file mode 100644
index 0000000000..38350ab8c4
--- /dev/null
+++ b/indra/viewer_components/manager/tests/with_setup_args.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+#
+# Taken from: https://gist.github.com/garyvdm/392ae20c673c7ee58d76
+
+"""
+@file with_setup_args.py
+@author garyvdm
+@date 2016-06-02
+"""
+
+
+def with_setup_args(setup, teardown=None):
+ """Decorator to add setup and/or teardown methods to a test function::
+ @with_setup_args(setup, teardown)
+ def test_something():
+ " ... "
+ The setup function should return (args, kwargs) which will be passed to
+ test function, and teardown function.
+ Note that `with_setup_args` is useful *only* for test functions, not for test
+ methods or inside of TestCase subclasses.
+ """
+ def decorate(func):
+ args = []
+ kwargs = {}
+
+ def test_wrapped():
+ func(*args, **kwargs)
+
+ test_wrapped.__name__ = func.__name__
+
+ def setup_wrapped():
+ a, k = setup()
+ args.extend(a)
+ kwargs.update(k)
+ if hasattr(func, 'setup'):
+ func.setup()
+ test_wrapped.setup = setup_wrapped
+
+ if teardown:
+ def teardown_wrapped():
+ if hasattr(func, 'teardown'):
+ func.teardown()
+ teardown(*args, **kwargs)
+
+ test_wrapped.teardown = teardown_wrapped
+ else:
+ if hasattr(func, 'teardown'):
+ test_wrapped.teardown = func.teardown()
+ return test_wrapped
+ return decorate \ No newline at end of file
diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py
new file mode 100755
index 0000000000..ff0f69a1b1
--- /dev/null
+++ b/indra/viewer_components/manager/update_manager.py
@@ -0,0 +1,512 @@
+#!/usr/bin/env python
+
+"""\
+@file update_manager.py
+@author coyot
+@date 2016-05-16
+@brief executes viewer update checking and manages downloading and applying of updates
+
+$LicenseInfo:firstyear=2016&license=viewerlgpl$
+Second Life Viewer Source Code
+Copyright (C) 2016, Linden Research, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation;
+version 2.1 of the License only.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+$/LicenseInfo$
+"""
+
+from llbase import llrest
+from llbase.llrest import RESTError
+from llbase import llsd
+from urlparse import urljoin
+
+import apply_update
+import download_update
+import errno
+import fnmatch
+import hashlib
+import InstallerUserMessage
+import json
+import os
+import platform
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import thread
+import urllib
+
+def silent_write(log_file_handle, text):
+ #if we have a log file, write. If not, do nothing.
+ #this is so we don't have to keep trapping for an exception with a None handle
+ #oh and because it is best effort, it is also a holey_write ;)
+ if (log_file_handle):
+ #prepend text for easy grepping
+ log_file_handle.write("UPDATE MANAGER: " + text + "\n")
+
+def after_frame(my_message, timeout = 10000):
+ #pop up a InstallerUserMessage.basic_message that kills itself after timeout milliseconds
+ #note that this blocks the caller for the duration of timeout
+ frame = InstallerUserMessage.InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
+ #this is done before basic_message so that we aren't blocked by mainloop()
+ frame.after(timeout, lambda: frame._delete_window)
+ frame.basic_message(message = my_message)
+
+def convert_version_file_style(version):
+ #converts a version string a.b.c.d to a_b_c_d as used in downloaded filenames
+ #re will throw a TypeError if it gets None, just return that.
+ try:
+ pattern = re.compile('\.')
+ return pattern.sub('_', version)
+ except TypeError, te:
+ return None
+
+def get_platform_key():
+ #this is the name that is inserted into the VVM URI
+ #and carried forward through the rest of the updater to determine
+ #platform specific actions as appropriate
+ platform_dict = {'Darwin':'mac', 'Linux':'lnx', 'Windows':'win'}
+ platform_uname = platform.system()
+ try:
+ return platform_dict[platform_uname]
+ except KeyError:
+ return None
+
+def get_summary(platform_name, launcher_path):
+ #get the contents of the summary.json file.
+ #for linux and windows, this file is in the same directory as the script
+ #for mac, the script is in ../Contents/MacOS/ and the file is in ../Contents/Resources/
+ script_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+ if (platform_name == 'mac'):
+ summary_dir = os.path.abspath(os.path.join(script_dir, "../Resources"))
+ else:
+ summary_dir = script_dir
+ summary_file = os.path.join(summary_dir,"summary.json")
+ with open(summary_file) as summary_handle:
+ return json.load(summary_handle)
+
+def get_parent_path(platform_name):
+ #find the parent of the logs and user_settings directories
+ if (platform_name == 'mac'):
+ settings_dir = os.path.join(os.path.expanduser('~'),'Library','Application Support','SecondLife')
+ elif (platform_name == 'lnx'):
+ settings_dir = os.path.join(os.path.expanduser('~'),'.secondlife')
+ #using list format of join is important here because the Windows pathsep in a string escapes the next char
+ elif (platform_name == 'win'):
+ settings_dir = os.path.join(os.path.expanduser('~'),'AppData','Roaming','SecondLife')
+ else:
+ settings_dir = None
+ return settings_dir
+
+def make_download_dir(parent_dir, new_version):
+ #make a canonical download dir if it does not already exist
+ #format: ../user_settings/downloads/1.2.3.456789
+ #we do this so that multiple viewers on the same host can update separately
+ #this also functions as a getter
+ try:
+ download_dir = os.path.join(parent_dir, "downloads", new_version)
+ os.makedirs(download_dir)
+ except OSError, hell:
+ #Directory already exists, that's okay. Other OSErrors are not okay.
+ if hell[0] == errno.EEXIST:
+ pass
+ else:
+ raise hell
+ return download_dir
+
+def check_for_completed_download(download_dir):
+ #there will be two files on completion, the download and a marker file called "".done""
+ #for optional upgrades, there may also be a .skip file to skip this particular upgrade
+ #or .next to install on next run
+ completed = None
+ marker_regex = '*' + '.done'
+ skip_regex = '*' + '.skip'
+ next_regex = '*' + '.next'
+ for filename in os.listdir(download_dir):
+ if fnmatch.fnmatch(filename, marker_regex):
+ completed = 'done'
+ elif fnmatch.fnmatch(filename, skip_regex):
+ completed = 'skip'
+ elif fnmatch.fnmatch(filename, next_regex):
+ #so we don't skip infinitely
+ os.remove(filename)
+ completed = 'next'
+ if not completed:
+ #cleanup
+ shutil.rmtree(download_dir)
+ return completed
+
+def get_settings(log_file_handle, parent_dir):
+ #return the settings file parsed into a dict
+ print str(parent_dir)
+ try:
+ settings_file = os.path.abspath(os.path.join(parent_dir,'user_settings','settings.xml'))
+ print "Settings file: " + str(settings_file)
+ settings = llsd.parse((open(settings_file)).read())
+ except llsd.LLSDParseError as lpe:
+ silent_write(log_file_handle, "Could not parse settings file %s" % lpe)
+ return None
+ return settings
+
+def get_log_file_handle(parent_dir):
+ #return a write handle on the log file
+ #plus log rotation and not dying on failure
+ log_file = os.path.join(parent_dir, 'update_manager.log')
+ old_file = log_file + '.old'
+ #if someone's log files are present but not writable, they've screwed up their install.
+ if os.access(log_file, os.W_OK):
+ if os.access(old_file, os.W_OK):
+ os.unlink(old_file)
+ os.rename(log_file, old_file)
+ elif not os.path.exists(log_file):
+ #reimplement TOUCH(1) in Python
+ #perms default to 644 which is fine
+ open(log_file, 'w+').close()
+ try:
+ f = open(log_file,'w+')
+ except Exception as e:
+ #we don't have a log file to write to, make a best effort and sally onward
+ print "Could not open update manager log file %s" % log_file
+ f = None
+ return f
+
+def make_VVM_UUID_hash(platform_key):
+ #NOTE: There is no python library support for a persistent machine specific UUID
+ # AND all three platforms do this a different way, so exec'ing out is really the best we can do
+ #Lastly, this is a best effort service. If we fail, we should still carry on with the update
+ uuid = None
+ if (platform_key == 'lnx'):
+ uuid = subprocess.check_output(['/usr/bin/hostid']).rstrip()
+ elif (platform_key == 'mac'):
+ #this is absurdly baroque
+ #/usr/sbin/system_profiler SPHardwareDataType | fgrep 'Serial' | awk '{print $NF}'
+ uuid = subprocess.check_output(["/usr/sbin/system_profiler", "SPHardwareDataType"])
+ #findall[0] does the grep for the value we are looking for: "Serial Number (system): XXXXXXXX"
+ #split(:)[1] gets us the XXXXXXX part
+ #lstrip shaves off the leading space that was after the colon
+ uuid = re.split(":", re.findall('Serial Number \(system\): \S*', uuid)[0])[1].lstrip()
+ elif (platform_key == 'win'):
+ # wmic csproduct get UUID | grep -v UUID
+ uuid = subprocess.check_output(['wmic','csproduct','get','UUID'])
+ #outputs in two rows:
+ #UUID
+ #XXXXXXX-XXXX...
+ uuid = re.split('\n',uuid)[1].rstrip()
+ if uuid is not None:
+ return hashlib.md5(uuid).hexdigest()
+ else:
+ #fake it
+ return hashlib.md5(str(uuid.uuid1())).hexdigest()
+
+def query_vvm(log_file_handle = None, platform_key = None, settings = None, summary_dict = None, UpdaterServiceURL = None, UpdaterWillingToTest = None):
+ result_data = None
+ #URI template /update/v1.1/channelname/version/platformkey/platformversion/willing-to-test/uniqueid
+ #https://wiki.lindenlab.com/wiki/Viewer_Version_Manager_REST_API#Viewer_Update_Query
+ if UpdaterServiceURL:
+ baseURI = UpdaterServiceURL
+ else:
+ base_URI = 'https://update.secondlife.com/update/'
+ channelname = summary_dict['Channel']
+ #this is kind of a mess because the settings value is a) in a map and b) is both the cohort and the version
+ version = summary_dict['Version']
+ platform_version = platform.release()
+ #this will always return something usable, error handling in method
+ hashed_UUID = make_VVM_UUID_hash(platform_key)
+ #note that this will not normally be in a settings.xml file and is only here for test builds.
+ #for test builds, add this key to the ../user_settings/settings.xml
+ """
+ <key>test</key>
+ <map>
+ <key>Comment</key>
+ <string>Tell update manager you aren't willing to test.</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <integer>testno</integer>
+ </map>
+ </map>
+ """
+ if UpdaterWillingToTest is not None:
+ if UpdaterWillingToTest:
+ test_ok = 'testok'
+ else:
+ test_ok = 'testno'
+ else:
+ try:
+ test_ok = settings['test']['Value']
+ except KeyError:
+ #normal case, no testing key
+ test_ok = 'testok'
+ UUID = make_VVM_UUID_hash(platform_key)
+ print repr(channelname)
+ print repr(version)
+ print repr(platform_key)
+ print repr(platform_version)
+ print repr(test_ok)
+ print repr(UUID)
+ #because urljoin can't be arsed to take multiple elements
+ #channelname is a list because although it can only be one word, it is a kind of argument and viewer args can take multiple keywords.
+ query_string = '/v1.0/' + channelname[0] + '/' + version + '/' + platform_key + '/' + platform_version + '/' + test_ok + '/' + UUID
+ VVMService = llrest.SimpleRESTService(name='VVM', baseurl=base_URI)
+ try:
+ result_data = VVMService.get(query_string)
+ except RESTError as re:
+ silent_write(log_file_handle, "Failed to query VVM using %s failed as %s" % (urljoin(base_URI,query_string), re))
+ return None
+ return result_data
+
+def download(url = None, version = None, download_dir = None, size = 0, background = False, chunk_size = 1024):
+ download_tries = 0
+ download_success = False
+ #for background execution
+ path_to_downloader = os.path.join(os.path.dirname(os.path.realpath(__file__)), "download_update.py")
+ #three strikes and you're out
+ while download_tries < 3 and not download_success:
+ #323: Check for a partial update of the required update; in either event, display an alert that a download is required, initiate the download, and then install and launch
+ if download_tries == 0:
+ after_frame(message = "Downloading new version " + version + " Please wait.")
+ else:
+ after_frame(message = "Trying again to download new version " + version + " Please wait.")
+ if not background:
+ try:
+ download_update.download_update(url = url, download_dir = download_dir, size = size, progressbar = True, chunk_size = chunk_size)
+ download_success = True
+ except:
+ download_tries += 1
+ silent_write(log_file_handle, "Failed to download new version " + version + ". Trying again.")
+ else:
+ try:
+ #Python does not have a facility to multithread a method, so we make the method a standalone
+ #and subprocess that
+ subprocess.call(path_to_downloader, "--url = %s --dir = %s --pb --size = %s --chunk_size = %s" % (url, download_dir, size, chunk_size))
+ download_success = True
+ except:
+ download_tries += 1
+ silent_write(log_file_handle, "Failed to download new version " + version + ". Trying again.")
+ if not download_success:
+ silent_write(log_file_handle, "Failed to download new version " + version)
+ after_frame(message = "Failed to download new version " + version + " Please check connectivity.")
+ return False
+ return True
+
+def install(platform_key = None, download_dir = None, log_file_handle = None, in_place = None, downloaded = None):
+ #user said no to this one
+ if downloaded != 'skip':
+ after_frame(message = "New version downloaded. Installing now, please wait.")
+ success = apply_update.apply_update(download_dir, platform_key, log_file_handle, in_place)
+ if success:
+ silent_write(log_file_handle, "successfully updated to " + version)
+ shutil.rmtree(download_dir)
+ #this is either True for in place or the path to the new install for not in place
+ return success
+ else:
+ after_frame(message = "Failed to apply " + version)
+ silent_write(log_file_handle, "Failed to update viewer to " + version)
+ return False
+
+def download_and_install(downloaded = None, url = None, version = None, download_dir = None, size = None,
+ platform_key = None, log_file_handle = None, in_place = None, chunk_size = 1024):
+ #extracted to a method because we do it twice in update_manager() and this makes the logic clearer
+ if not downloaded:
+ #do the download, exit if we fail
+ if not download(url = url, version = version, download_dir = download_dir, size = size, chunk_size = chunk_size):
+ return (False, 'download', version)
+ #do the install
+ path_to_new_launcher = install(platform_key = platform_key, download_dir = download_dir,
+ log_file_handle = log_file_handle, in_place = in_place, downloaded = downloaded)
+ if path_to_new_launcher:
+ #if we succeed, propagate the success type upwards
+ if in_place:
+ return (True, 'in place', True)
+ else:
+ return (True, 'in place', path_to_new_launcher)
+ else:
+ #propagate failure
+ return (False, 'apply', version)
+
+def update_manager(cli_overrides = None):
+ #cli_overrides is a dict where the keys are specific parameters of interest and the values are the arguments to
+ #comments that begin with '323:' are steps taken from the algorithm in the description of SL-323.
+ # Note that in the interest of efficiency, such as determining download success once at the top
+ # The code does follow precisely the same order as the algorithm.
+ #return values rather than exit codes. All of them are to communicate with launcher
+ #we print just before we return so that __main__ outputs something - returns are swallowed
+ # (False, 'setup', None): error occurred before we knew what the update was (e.g., in setup or parsing)
+ # (False, 'download', version): we failed to download the new version
+ # (False, 'apply', version): we failed to apply the new version
+ # (True, None, None): No update found
+ # (True, 'in place, True): update applied in place
+ # (True, 'in place', path_to_new_launcher): Update applied by a new install to a new location
+ # (True, 'background', True): background download initiated
+
+ #setup and getting initial parameters
+ platform_key = get_platform_key()
+ parent_dir = get_parent_path(platform_key)
+ log_file_handle = get_log_file_handle(parent_dir)
+ settings = None
+ print "cli_overrides: " + str(cli_overrides)
+
+ #check to see if user has install rights
+ #get the owner of the install and the current user
+ script_owner_id = os.stat(os.path.realpath(__file__)).st_uid
+ user_id = os.geteuid()
+ #if we are on lnx or mac, we can pretty print the IDs as names using the pwd module
+ #win does not provide this support and Python will throw an ImportError there, so just use raw IDs
+ if script_owner_id != user_id:
+ if platform_key != 'win':
+ import pwd
+ script_owner_name = pwd.getpwuid(script_owner_id)[0]
+ username = pwd.getpwuid(user_id)[0]
+ else:
+ username = user_id
+ script_owner_name = script_owner_id
+ silent_write(log_file_handle, "Upgrade notification attempted by userid " + username)
+ frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
+ frame.binary_choice_message(message = "Second Life was installed by userid " + script_owner_name
+ + ". Do you have privileges to install?", true = "Yes", false = 'No')
+ if not frame.choice.get():
+ silent_write(log_file_handle, "Upgrade attempt declined by userid " + username)
+ after_frame(message = "Please find a system admin to upgrade Second Life")
+ print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
+ return (False, 'setup', None)
+
+ if cli_overrides is not None:
+ if '--settings' in cli_overrides.keys():
+ if cli_overrides['--settings'] is not None:
+ settings = get_settings(log_file_handle, cli_overrides['--settings'])
+ else:
+ settings = get_settings(log_file_handle, parent_dir)
+
+ if settings is None:
+ silent_write(log_file_handle, "Failed to load viewer settings")
+ print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
+ return (False, 'setup', None)
+
+ #323: If a complete download of that update is found, check the update preference:
+ #settings['UpdaterServiceSetting'] = 0 is manual install
+ """ssh://hg@bitbucket.org/lindenlab/viewer-release-maint-6585
+ <key>UpdaterServiceSetting</key>
+ <map>
+ <key>Comment</key>
+ <string>Configure updater service.</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <string>0</string>
+ </map>
+ """
+ if cli_overrides is not None:
+ if '--set' in cli_overrides.keys():
+ if 'UpdaterServiceSetting' in cli_overrides['--set'].keys():
+ install_automatically = cli_overrides['--set']['UpdaterServiceSetting']
+ else:
+ try:
+ install_automatically = settings['UpdaterServiceSetting']['Value']
+ #because, for some godforsaken reason, we delete the setting rather than changing the value
+ except KeyError:
+ install_automatically = 1
+
+ #use default chunk size if none is given
+ if cli_overrides is not None:
+ if '--set' in cli_overrides.keys():
+ if 'UpdaterMaximumBandwidth' in cli_overrides['--set'].keys():
+ chunk_size = cli_overrides['--set']['UpdaterMaximumBandwidth']
+ else:
+ chunk_size = 1024
+
+ #get channel and version
+ try:
+ summary_dict = get_summary(platform_key, os.path.abspath(os.path.realpath(__file__)))
+ if cli_overrides is not None:
+ if 'channel' in cli_overrides.keys():
+ summary_dict['Channel'] = cli_overrides['channel']
+ except Exception, e:
+ silent_write(log_file_handle, "Could not obtain channel and version, exiting.")
+ silent_write(log_file_handle, e.message)
+ print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
+ return (False, 'setup', None)
+
+ #323: On launch, the Viewer Manager should query the Viewer Version Manager update api.
+ if cli_overrides is not None:
+ if '--update-service' in cli_overrides.keys():
+ UpdaterServiceURL = cli_overrides['--update-service']
+ else:
+ #tells query_vvm to use the default
+ UpdaterServiceURL = None
+ result_data = query_vvm(log_file_handle, platform_key, settings, summary_dict, UpdaterServiceURL)
+ #nothing to do or error
+ if not result_data:
+ silent_write(log_file_handle, "No update found.")
+ print "Update manager exited with (%s, %s, %s)" % (True, None, None)
+ return (True, None, None)
+
+ #get download directory, if there are perm issues or similar problems, give up
+ try:
+ download_dir = make_download_dir(parent_dir, result_data['version'])
+ except Exception, e:
+ print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
+ return (False, 'setup', None)
+
+ #if the channel name of the response is the same as the channel we are launched from, the update is "in place"
+ #and launcher will launch the viewer in this install location. Otherwise, it will launch the Launcher from
+ #the new location and kill itself.
+ in_place = (summary_dict['Channel'] == result_data['channel'])
+
+ #determine if we've tried this download before
+ downloaded = check_for_completed_download(download_dir)
+
+ #323: If the response indicates that there is a required update:
+ if result_data['required'] or (not result_data['required'] and install_automatically):
+ #323: Check for a completed download of the required update; if found, display an alert, install the required update, and launch the newly installed viewer.
+ #323: If [optional download and] Install Automatically: display an alert, install the update and launch updated viewer.
+ return download_and_install(downloaded = downloaded, url = result_data['url'], version = result_data['version'], download_dir = download_dir,
+ size = result_data['size'], platform_key = platform_key, log_file_handle = log_file_handle, in_place = in_place, chunk_size = chunk_size)
+ else:
+ #323: If the update response indicates that there is an optional update:
+ #323: Check to see if the optional update has already been downloaded.
+ #323: If a complete download of that update is found, check the update preference:
+ #note: automatic install handled above as the steps are the same as required upgrades
+ #323: If Install Manually: display a message with the update information and ask the user whether or not to install the update with three choices:
+ #323: Skip this update: create a marker that subsequent launches should not prompt for this update as long as it is optional,
+ # but leave the download in place so that if it becomes required it will be there.
+ #323: Install next time: create a marker that skips the prompt and installs on the next launch
+ #323: Install and launch now: do it.
+ if downloaded is not None and downloaded != 'skip':
+ frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
+ #The choices are reordered slightly to encourage immediate install and slightly discourage skipping
+ frame.trinary_message(message = "Please make a selection",
+ one = "Install new version now.", two = 'Install the next time the viewer is launched.', three = 'Skip this update.')
+ choice = frame.choice.get()
+ if choice == 1:
+ return download_and_install(downloaded = downloaded, url = result_data['url'], version = result_data['version'], download_dir = download_dir,
+ size = result_data['size'], platform_key = platform_key, log_file_handle = log_file_handle, in_place = in_place, chunk_size = chunk_size)
+ elif choice == 2:
+ tempfile.mkstmp(suffix = ".next", dir = download_dir)
+ return (True, None, None)
+ else:
+ tempfile.mkstmp(suffix = ".skip", dir = download_dir)
+ return (True, None, None)
+ else:
+ #multithread a download
+ download(url = result_data['url'], version = result_data['version'], download_dir = download_dir, size = result_data['size'], background = True)
+ print "Update manager exited with (%s, %s, %s)" % (True, 'background', True)
+ return (True, 'background', True)
+
+
+if __name__ == '__main__':
+ #there is no argument parsing or other main() work to be done
+ update_manager()