aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim
diff options
context:
space:
mode:
authorJustin Clarke Casey2008-03-29 22:18:44 +0000
committerJustin Clarke Casey2008-03-29 22:18:44 +0000
commit875211b262ecda2eced68b217221dead20cbdb6b (patch)
treee84ecb6149076e71ce6d9fedeea5f0c49ca46d93 /OpenSim
parent* Updating ODE Libraries to release / dNODEBUG (diff)
downloadopensim-SC-875211b262ecda2eced68b217221dead20cbdb6b.zip
opensim-SC-875211b262ecda2eced68b217221dead20cbdb6b.tar.gz
opensim-SC-875211b262ecda2eced68b217221dead20cbdb6b.tar.bz2
opensim-SC-875211b262ecda2eced68b217221dead20cbdb6b.tar.xz
* Various 1.19.0.5 grid inventory request fixes. These will only take affect once the region and the grid servers have upgraded to this revision
* You may also need to clear your cache before seeing any effect. * These fixes may or may not affect inventory on the RC client. * These fixes should make non-root folders work better, stop inventory failure on first login, allow trash to be emptied and make texture picker in object edit view work properly * Fixes are 1) make initial root folder request wait for async inventory delivery, 2) deliver all folders in the initial login skeleton, not just the root child ones and 3) deal with situations where we receive child folders from the inventory service before their parent is received.
Diffstat (limited to 'OpenSim')
-rw-r--r--OpenSim/Framework/Communications/Cache/CachedUserInfo.cs128
-rw-r--r--OpenSim/Framework/Communications/Cache/UserProfileCacheService.cs35
-rw-r--r--OpenSim/Framework/Communications/InventoryServiceBase.cs21
-rw-r--r--OpenSim/Framework/Communications/LoginService.cs2
-rw-r--r--OpenSim/Grid/InventoryServer/GridInventoryService.cs18
-rw-r--r--OpenSim/Grid/InventoryServer/Main.cs31
-rw-r--r--OpenSim/Grid/UserServer/UserLoginService.cs2
-rw-r--r--OpenSim/Region/ClientStack/ClientView.cs5
8 files changed, 195 insertions, 47 deletions
diff --git a/OpenSim/Framework/Communications/Cache/CachedUserInfo.cs b/OpenSim/Framework/Communications/Cache/CachedUserInfo.cs
index 59e5b6e..f2dd2bf 100644
--- a/OpenSim/Framework/Communications/Cache/CachedUserInfo.cs
+++ b/OpenSim/Framework/Communications/Cache/CachedUserInfo.cs
@@ -25,6 +25,9 @@
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */ 26 */
27 27
28using System;
29using System.Collections.Generic;
30
28using libsecondlife; 31using libsecondlife;
29 32
30namespace OpenSim.Framework.Communications.Cache 33namespace OpenSim.Framework.Communications.Cache
@@ -35,50 +38,135 @@ namespace OpenSim.Framework.Communications.Cache
35 = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 38 = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
36 39
37 private readonly CommunicationsManager m_parentCommsManager; 40 private readonly CommunicationsManager m_parentCommsManager;
38 // Fields 41
42 // FIXME: These need to be hidden behind accessors
39 public InventoryFolderImpl RootFolder = null; 43 public InventoryFolderImpl RootFolder = null;
40 public UserProfileData UserProfile = null; 44 public UserProfileData UserProfile = null;
45
46 /// <summary>
47 /// Stores received folders for which we have not yet received the parents.
48 /// </summary></param>
49 private IDictionary<LLUUID, IList<InventoryFolderImpl>> pendingCategorizationFolders
50 = new Dictionary<LLUUID, IList<InventoryFolderImpl>>();
41 51
42 public CachedUserInfo(CommunicationsManager commsManager) 52 public CachedUserInfo(CommunicationsManager commsManager)
43 { 53 {
44 m_parentCommsManager = commsManager; 54 m_parentCommsManager = commsManager;
45 } 55 }
46 56
47 // Methods 57 /// <summary>
48 public void FolderReceive(LLUUID userID, InventoryFolderImpl folderInfo) 58 /// Store a folder pending categorization when its parent is received.
59 /// </summary>
60 /// <param name="folder"></param>
61 private void AddPendingFolder(InventoryFolderImpl folder)
49 { 62 {
50 //m_log.DebugFormat("[INVENTORY CACHE]: Received folder {0} {1} for user {2}", folderInfo.name, folderInfo.folderID, userID); 63 LLUUID parentFolderId = folder.parentID;
51 64
52 if (userID == UserProfile.UUID) 65 if (pendingCategorizationFolders.ContainsKey(parentFolderId))
66 {
67 pendingCategorizationFolders[parentFolderId].Add(folder);
68 }
69 else
70 {
71 IList<InventoryFolderImpl> folders = new List<InventoryFolderImpl>();
72 folders.Add(folder);
73
74 pendingCategorizationFolders[parentFolderId] = folders;
75 }
76 }
77
78 /// <summary>
79 /// Add any pending folders which are children of parent
80 /// </summary>
81 /// <param name="parentId">
82 /// A <see cref="LLUUID"/>
83 /// </param>
84 private void ResolvePendingFolders(InventoryFolderImpl parent)
85 {
86 if (pendingCategorizationFolders.ContainsKey(parent.folderID))
53 { 87 {
54 if (RootFolder == null) 88 foreach (InventoryFolderImpl folder in pendingCategorizationFolders[parent.folderID])
55 { 89 {
56 if (folderInfo.parentID == LLUUID.Zero) 90// m_log.DebugFormat(
91// "[INVENTORY CACHE]: Resolving pending received folder {0} {1} into {2} {3}",
92// folder.name, folder.folderID, parent.name, parent.folderID);
93
94 if (!parent.SubFolders.ContainsKey(folder.folderID))
57 { 95 {
58 RootFolder = folderInfo; 96 parent.SubFolders.Add(folder.folderID, folder);
59 } 97 }
60 } 98 }
61 else if (RootFolder.folderID == folderInfo.parentID) 99 }
100 }
101
102 /// <summary>
103 /// Callback invoked when a folder is received from an async request to the inventory service.
104 /// </summary>
105 /// <param name="userID"></param>
106 /// <param name="folderInfo"></param>
107 public void FolderReceive(LLUUID userID, InventoryFolderImpl folderInfo)
108 {
109 // FIXME: Exceptions thrown upwards never appear on the console. Could fix further up if these
110 // are simply being swallowed
111 try
112 {
113// m_log.DebugFormat(
114// "[INVENTORY CACHE]: Received folder {0} {1} for user {2}",
115// folderInfo.name, folderInfo.folderID, userID);
116
117 if (userID == UserProfile.UUID)
62 { 118 {
63 if (!RootFolder.SubFolders.ContainsKey(folderInfo.folderID)) 119 if (RootFolder == null)
64 { 120 {
65 RootFolder.SubFolders.Add(folderInfo.folderID, folderInfo); 121 if (folderInfo.parentID == LLUUID.Zero)
122 {
123 RootFolder = folderInfo;
124 }
66 } 125 }
67 } 126 else if (RootFolder.folderID == folderInfo.parentID)
68 else
69 {
70 InventoryFolderImpl folder = RootFolder.HasSubFolder(folderInfo.parentID);
71 if (folder != null)
72 { 127 {
73 if (!folder.SubFolders.ContainsKey(folderInfo.folderID)) 128 if (!RootFolder.SubFolders.ContainsKey(folderInfo.folderID))
74 { 129 {
75 folder.SubFolders.Add(folderInfo.folderID, folderInfo); 130 RootFolder.SubFolders.Add(folderInfo.folderID, folderInfo);
76 } 131 }
132 else
133 {
134 AddPendingFolder(folderInfo);
135 }
77 } 136 }
137 else
138 {
139 InventoryFolderImpl folder = RootFolder.HasSubFolder(folderInfo.parentID);
140 if (folder != null)
141 {
142 if (!folder.SubFolders.ContainsKey(folderInfo.folderID))
143 {
144 folder.SubFolders.Add(folderInfo.folderID, folderInfo);
145 }
146 }
147 else
148 {
149 AddPendingFolder(folderInfo);
150 }
151 }
152
153 ResolvePendingFolders(folderInfo);
78 } 154 }
79 } 155 }
156 catch (Exception e)
157 {
158 m_log.ErrorFormat("[INVENTORY CACHE] {0}", e);
159 }
80 } 160 }
81 161
162 /// <summary>
163 /// Callback invoked when an item is received from an async request to the inventory service.
164 ///
165 /// FIXME: We're assuming here that items are always received after all the folders have been
166 /// received.
167 /// </summary>
168 /// <param name="userID"></param>
169 /// <param name="folderInfo"></param>
82 public void ItemReceive(LLUUID userID, InventoryItemBase itemInfo) 170 public void ItemReceive(LLUUID userID, InventoryItemBase itemInfo)
83 { 171 {
84 if ((userID == UserProfile.UUID) && (RootFolder != null)) 172 if ((userID == UserProfile.UUID) && (RootFolder != null))
diff --git a/OpenSim/Framework/Communications/Cache/UserProfileCacheService.cs b/OpenSim/Framework/Communications/Cache/UserProfileCacheService.cs
index 67022c7..c3f51da 100644
--- a/OpenSim/Framework/Communications/Cache/UserProfileCacheService.cs
+++ b/OpenSim/Framework/Communications/Cache/UserProfileCacheService.cs
@@ -27,7 +27,10 @@
27 27
28using System; 28using System;
29using System.Collections.Generic; 29using System.Collections.Generic;
30using System.Threading;
31
30using libsecondlife; 32using libsecondlife;
33
31using OpenSim.Framework.Console; 34using OpenSim.Framework.Console;
32 35
33namespace OpenSim.Framework.Communications.Cache 36namespace OpenSim.Framework.Communications.Cache
@@ -65,9 +68,7 @@ namespace OpenSim.Framework.Communications.Cache
65 68
66 if (userInfo.UserProfile != null) 69 if (userInfo.UserProfile != null)
67 { 70 {
68 // The request itself will occur when the agent finishes logging on to the region 71 // The inventory will be populated when the user actually enters the scene
69 // so there's no need to do it here.
70 //RequestInventoryForUser(userID, userInfo);
71 m_userProfiles.Add(userID, userInfo); 72 m_userProfiles.Add(userID, userInfo);
72 } 73 }
73 else 74 else
@@ -219,10 +220,34 @@ namespace OpenSim.Framework.Communications.Cache
219 CachedUserInfo userProfile; 220 CachedUserInfo userProfile;
220 if (m_userProfiles.TryGetValue(remoteClient.AgentId, out userProfile)) 221 if (m_userProfiles.TryGetValue(remoteClient.AgentId, out userProfile))
221 { 222 {
223 // XXX: When a client crosses into a scene, their entire inventory is fetched
224 // asynchronously. However, if the client is logging on and does not have a cached root
225 // folder, then the root folder request usually comes in *before* the async completes, leading to
226 // inventory failure.
227 //
228 // This is a crude way of dealing with that by retrying the lookup.
229 if (userProfile.RootFolder == null)
230 {
231 int attempts = 5;
232 while (attempts-- > 0)
233 {
234 Thread.Sleep(3000);
235
236 if (userProfile.RootFolder != null)
237 {
238 break;
239 }
240 }
241 }
242
222 if (userProfile.RootFolder != null) 243 if (userProfile.RootFolder != null)
223 { 244 {
224 if (userProfile.RootFolder.folderID == folderID) 245 if (userProfile.RootFolder.folderID == folderID)
225 { 246 {
247// m_log.DebugFormat(
248// "[AGENT INVENTORY]: Found root folder {0} for client {1}",
249// folderID, remoteClient.AgentId);
250
226 remoteClient.SendInventoryFolderDetails( 251 remoteClient.SendInventoryFolderDetails(
227 remoteClient.AgentId, folderID, userProfile.RootFolder.RequestListOfItems(), 252 remoteClient.AgentId, folderID, userProfile.RootFolder.RequestListOfItems(),
228 userProfile.RootFolder.RequestListOfFolders(), 253 userProfile.RootFolder.RequestListOfFolders(),
@@ -234,6 +259,10 @@ namespace OpenSim.Framework.Communications.Cache
234 { 259 {
235 if ((fold = userProfile.RootFolder.HasSubFolder(folderID)) != null) 260 if ((fold = userProfile.RootFolder.HasSubFolder(folderID)) != null)
236 { 261 {
262// m_log.DebugFormat(
263// "[AGENT INVENTORY]: Found folder {0} for client {1}",
264// folderID, remoteClient.AgentId);
265
237 remoteClient.SendInventoryFolderDetails( 266 remoteClient.SendInventoryFolderDetails(
238 remoteClient.AgentId, folderID, fold.RequestListOfItems(), 267 remoteClient.AgentId, folderID, fold.RequestListOfItems(),
239 fold.RequestListOfFolders(), fetchFolders, fetchItems); 268 fold.RequestListOfFolders(), fetchFolders, fetchItems);
diff --git a/OpenSim/Framework/Communications/InventoryServiceBase.cs b/OpenSim/Framework/Communications/InventoryServiceBase.cs
index 6e909da..5515c77 100644
--- a/OpenSim/Framework/Communications/InventoryServiceBase.cs
+++ b/OpenSim/Framework/Communications/InventoryServiceBase.cs
@@ -76,12 +76,27 @@ namespace OpenSim.Framework.Communications
76 76
77 #region IInventoryServices methods 77 #region IInventoryServices methods
78 78
79 // See IInventoryServices 79 /// <summary>
80 /// Guid to UUID wrapper for same name IInventoryServices method
81 /// </summary>
82 /// <param name="rawUserID"></param>
83 /// <returns></returns>
80 public List<InventoryFolderBase> RequestFirstLevelFolders(Guid rawUserID) 84 public List<InventoryFolderBase> RequestFirstLevelFolders(Guid rawUserID)
81 { 85 {
82 LLUUID userID = new LLUUID(rawUserID); 86 LLUUID userID = new LLUUID(rawUserID);
83 return RequestFirstLevelFolders(userID); 87 return RequestFirstLevelFolders(userID);
84 } 88 }
89
90 /// <summary>
91 /// Guid to UUID wrapper for same name IInventoryServices method
92 /// </summary>
93 /// <param name="rawUserID"></param>
94 /// <returns></returns>
95 public List<InventoryFolderBase> GetInventorySkeleton(Guid rawUserID)
96 {
97 LLUUID userID = new LLUUID(rawUserID);
98 return GetInventorySkeleton(userID);
99 }
85 100
86 // See IInventoryServices 101 // See IInventoryServices
87 public List<InventoryFolderBase> RequestFirstLevelFolders(LLUUID userID) 102 public List<InventoryFolderBase> RequestFirstLevelFolders(LLUUID userID)
@@ -112,7 +127,7 @@ namespace OpenSim.Framework.Communications
112 // See IInventoryServices 127 // See IInventoryServices
113 public List<InventoryFolderBase> GetInventorySkeleton(LLUUID userId) 128 public List<InventoryFolderBase> GetInventorySkeleton(LLUUID userId)
114 { 129 {
115// m_log.DebugFormat("[AGENT INVENTORY]: Getting inventory skeleton for {0}", userId); 130 m_log.DebugFormat("[AGENT INVENTORY]: Getting inventory skeleton for {0}", userId);
116 131
117 List<InventoryFolderBase> userFolders = new List<InventoryFolderBase>(); 132 List<InventoryFolderBase> userFolders = new List<InventoryFolderBase>();
118 133
@@ -173,7 +188,7 @@ namespace OpenSim.Framework.Communications
173 188
174 if (null != existingRootFolder) 189 if (null != existingRootFolder)
175 { 190 {
176 m_log.ErrorFormat("[AGENTINVENTORY]: " + 191 m_log.ErrorFormat("[AGENT INVENTORY]: " +
177 "Did not create a new inventory for user {0} since they already have " 192 "Did not create a new inventory for user {0} since they already have "
178 + "a root inventory folder with id {1}", user, existingRootFolder); 193 + "a root inventory folder with id {1}", user, existingRootFolder);
179 } 194 }
diff --git a/OpenSim/Framework/Communications/LoginService.cs b/OpenSim/Framework/Communications/LoginService.cs
index e738d0b..cbe8783 100644
--- a/OpenSim/Framework/Communications/LoginService.cs
+++ b/OpenSim/Framework/Communications/LoginService.cs
@@ -272,7 +272,7 @@ namespace OpenSim.Framework.UserManagement
272 } 272 }
273 catch (Exception e) 273 catch (Exception e)
274 { 274 {
275 m_log.Info("[LOGIN]: Login failed, exception" + e.ToString()); 275 m_log.Info("[LOGIN]: Login failed, " + e.ToString());
276 } 276 }
277 } 277 }
278 278
diff --git a/OpenSim/Grid/InventoryServer/GridInventoryService.cs b/OpenSim/Grid/InventoryServer/GridInventoryService.cs
index ea85d5f..cfe06e8 100644
--- a/OpenSim/Grid/InventoryServer/GridInventoryService.cs
+++ b/OpenSim/Grid/InventoryServer/GridInventoryService.cs
@@ -108,8 +108,7 @@ namespace OpenSim.Grid.InventoryServer
108 { 108 {
109 LLUUID userID = new LLUUID(rawUserID); 109 LLUUID userID = new LLUUID(rawUserID);
110 110
111 // We get enough verbose messages later on for diagnostics 111 m_log.Info("[GRID INVENTORY]: Request for inventory of " + userID.ToString());
112 //m_log.Info("[INVENTORY]: Request for inventory for " + userID.ToString());
113 112
114 InventoryCollection invCollection = new InventoryCollection(); 113 InventoryCollection invCollection = new InventoryCollection();
115 List<InventoryFolderBase> folders; 114 List<InventoryFolderBase> folders;
@@ -120,6 +119,21 @@ namespace OpenSim.Grid.InventoryServer
120 invCollection.Folders = folders; 119 invCollection.Folders = folders;
121 invCollection.UserID = userID; 120 invCollection.UserID = userID;
122 } 121 }
122
123// foreach (InventoryFolderBase folder in folders)
124// {
125// m_log.DebugFormat(
126// "[GRID INVENTORY]: Sending back folder {0}, {1}",
127// folder.name, folder.folderID);
128// }
129//
130// foreach (InventoryItemBase item in allItems)
131// {
132// m_log.DebugFormat(
133// "[GRID INVENTORY]: Sending back item {0}, {1}, folder {2}",
134// item.inventoryName, item.inventoryID, item.parentFolderID);
135// }
136
123 return invCollection; 137 return invCollection;
124 } 138 }
125 139
diff --git a/OpenSim/Grid/InventoryServer/Main.cs b/OpenSim/Grid/InventoryServer/Main.cs
index ea056a0..009f3f1 100644
--- a/OpenSim/Grid/InventoryServer/Main.cs
+++ b/OpenSim/Grid/InventoryServer/Main.cs
@@ -83,29 +83,32 @@ namespace OpenSim.Grid.InventoryServer
83 protected void AddHttpHandlers() 83 protected void AddHttpHandlers()
84 { 84 {
85 m_httpServer.AddStreamHandler( 85 m_httpServer.AddStreamHandler(
86 new RestDeserialisehandler<Guid, InventoryCollection>("POST", "/GetInventory/", 86 new RestDeserialisehandler<Guid, InventoryCollection>(
87 m_inventoryService.GetUserInventory)); 87 "POST", "/GetInventory/", m_inventoryService.GetUserInventory));
88
88 m_httpServer.AddStreamHandler( 89 m_httpServer.AddStreamHandler(
89 new RestDeserialisehandler<Guid, bool>("POST", "/CreateInventory/", 90 new RestDeserialisehandler<Guid, bool>(
90 m_inventoryService.CreateUsersInventory)); 91 "POST", "/CreateInventory/", m_inventoryService.CreateUsersInventory));
92
91 m_httpServer.AddStreamHandler( 93 m_httpServer.AddStreamHandler(
92 new RestDeserialisehandler<InventoryFolderBase, bool>("POST", "/NewFolder/", 94 new RestDeserialisehandler<InventoryFolderBase, bool>(
93 m_inventoryService.AddInventoryFolder)); 95 "POST", "/NewFolder/", m_inventoryService.AddInventoryFolder));
94 96
95 m_httpServer.AddStreamHandler( 97 m_httpServer.AddStreamHandler(
96 new RestDeserialisehandler<InventoryFolderBase, bool>("POST", "/MoveFolder/", 98 new RestDeserialisehandler<InventoryFolderBase, bool>(
97 m_inventoryService.MoveInventoryFolder)); 99 "POST", "/MoveFolder/", m_inventoryService.MoveInventoryFolder));
98 100
99 m_httpServer.AddStreamHandler( 101 m_httpServer.AddStreamHandler(
100 new RestDeserialisehandler<InventoryItemBase, bool>("POST", "/NewItem/", 102 new RestDeserialisehandler<InventoryItemBase, bool>(
101 m_inventoryService.AddInventoryItem)); 103 "POST", "/NewItem/", m_inventoryService.AddInventoryItem));
104
102 m_httpServer.AddStreamHandler( 105 m_httpServer.AddStreamHandler(
103 new RestDeserialisehandler<InventoryItemBase, bool>("POST", "/DeleteItem/", 106 new RestDeserialisehandler<InventoryItemBase, bool>(
104 m_inventoryService.DeleteInvItem)); 107 "POST", "/DeleteItem/", m_inventoryService.DeleteInvItem));
105 108
106 m_httpServer.AddStreamHandler( 109 m_httpServer.AddStreamHandler(
107 new RestDeserialisehandler<Guid, List<InventoryFolderBase>>("POST", "/RootFolders/", 110 new RestDeserialisehandler<Guid, List<InventoryFolderBase>>
108 m_inventoryService.RequestFirstLevelFolders)); 111 ("POST", "/RootFolders/", m_inventoryService.GetInventorySkeleton));
109 112
110 // httpServer.AddStreamHandler(new InventoryManager.GetInventory(m_inventoryManager)); 113 // httpServer.AddStreamHandler(new InventoryManager.GetInventory(m_inventoryManager));
111 } 114 }
diff --git a/OpenSim/Grid/UserServer/UserLoginService.cs b/OpenSim/Grid/UserServer/UserLoginService.cs
index e95acac..6d3a081 100644
--- a/OpenSim/Grid/UserServer/UserLoginService.cs
+++ b/OpenSim/Grid/UserServer/UserLoginService.cs
@@ -323,6 +323,8 @@ namespace OpenSim.Grid.UserServer
323 Hashtable TempHash; 323 Hashtable TempHash;
324 foreach (InventoryFolderBase InvFolder in folders) 324 foreach (InventoryFolderBase InvFolder in folders)
325 { 325 {
326// m_log.DebugFormat("[LOGIN]: Received agent inventory folder {0}", InvFolder.name);
327
326 if (InvFolder.parentID == LLUUID.Zero) 328 if (InvFolder.parentID == LLUUID.Zero)
327 { 329 {
328 rootID = InvFolder.folderID; 330 rootID = InvFolder.folderID;
diff --git a/OpenSim/Region/ClientStack/ClientView.cs b/OpenSim/Region/ClientStack/ClientView.cs
index 971c295..a8762e4 100644
--- a/OpenSim/Region/ClientStack/ClientView.cs
+++ b/OpenSim/Region/ClientStack/ClientView.cs
@@ -1148,10 +1148,7 @@ namespace OpenSim.Region.ClientStack
1148 public void SendInventoryFolderDetails(LLUUID ownerID, LLUUID folderID, List<InventoryItemBase> items, 1148 public void SendInventoryFolderDetails(LLUUID ownerID, LLUUID folderID, List<InventoryItemBase> items,
1149 List<InventoryFolderBase> folders, 1149 List<InventoryFolderBase> folders,
1150 bool fetchFolders, bool fetchItems) 1150 bool fetchFolders, bool fetchItems)
1151 { 1151 {
1152 // XXX Very temporarily, always fetch the folders
1153 fetchFolders = true;
1154
1155 // An inventory descendents packet consists of a single agent section and an inventory details 1152 // An inventory descendents packet consists of a single agent section and an inventory details
1156 // section for each inventory item. The size of each inventory item is approximately 550 bytes. 1153 // section for each inventory item. The size of each inventory item is approximately 550 bytes.
1157 // In theory, UDP has a maximum packet size of 64k, so it should be possible to send descendent 1154 // In theory, UDP has a maximum packet size of 64k, so it should be possible to send descendent