mantis 8651: try to fix items embedded in notecards. This may need more work
							parent
							
								
									3196d2fa6f
								
							
						
					
					
						commit
						18f2e25b23
					
				|  | @ -28,6 +28,7 @@ | |||
| using OpenMetaverse; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Globalization; | ||||
| 
 | ||||
| namespace OpenSim.Framework | ||||
| { | ||||
|  | @ -190,6 +191,56 @@ namespace OpenSim.Framework | |||
|         private static Dictionary<sbyte, string> inventory2Content; | ||||
|         private static Dictionary<string, sbyte> content2Asset; | ||||
|         private static Dictionary<string, sbyte> content2Inventory; | ||||
|         private static Dictionary<string, AssetType> name2Asset = new Dictionary<string, AssetType>() | ||||
|         { | ||||
|             {"texture", AssetType.Texture }, | ||||
|             {"sound", AssetType.Sound}, | ||||
|             {"callcard", AssetType.CallingCard}, | ||||
|             {"landmark", AssetType.Landmark}, | ||||
|             {"script", (AssetType)4}, | ||||
|             {"clothing", AssetType.Clothing}, | ||||
|             {"object", AssetType.Object}, | ||||
|             {"notecard", AssetType.Notecard}, | ||||
|             {"category", AssetType.Folder}, | ||||
|             {"lsltext", AssetType.LSLText}, | ||||
|             {"lslbyte", AssetType.LSLBytecode}, | ||||
|             {"txtr_tga", AssetType.TextureTGA}, | ||||
|             {"bodypart", AssetType.Bodypart}, | ||||
|             {"snd_wav", AssetType.SoundWAV}, | ||||
|             {"img_tga", AssetType.ImageTGA}, | ||||
|             {"jpeg", AssetType.ImageJPEG}, | ||||
|             {"animatn", AssetType.Animation}, | ||||
|             {"gesture", AssetType.Gesture}, | ||||
|             {"simstate", AssetType.Simstate}, | ||||
|             {"mesh", AssetType.Mesh} | ||||
| //            "settings", AssetType.Settings} | ||||
|         }; | ||||
|         private static Dictionary<string, FolderType> name2Inventory = new Dictionary<string, FolderType>() | ||||
|         { | ||||
|             {"texture", FolderType.Texture}, | ||||
|             {"sound", FolderType.Sound}, | ||||
|             {"callcard", FolderType.CallingCard}, | ||||
|             {"landmark", FolderType.Landmark}, | ||||
|             {"script", (FolderType)4}, | ||||
|             {"clothing", FolderType.Clothing}, | ||||
|             {"object", FolderType.Object}, | ||||
|             {"notecard", FolderType.Notecard}, | ||||
|             {"root", FolderType.Root}, | ||||
|             {"lsltext", FolderType.LSLText}, | ||||
|             {"bodypart", FolderType.BodyPart}, | ||||
|             {"trash", FolderType.Trash}, | ||||
|             {"snapshot", FolderType.Snapshot}, | ||||
|             {"lostandfound", FolderType.LostAndFound}, | ||||
|             {"animatn", FolderType.Animation}, | ||||
|             {"gesture", FolderType.Gesture}, | ||||
|             {"favorites", FolderType.Favorites}, | ||||
|             {"currentoutfit", FolderType.CurrentOutfit}, | ||||
|             {"outfit", FolderType.Outfit}, | ||||
|             {"myoutfits", FolderType.MyOutfits}, | ||||
|             {"mesh", FolderType.Mesh}, | ||||
| //            "settings", FolderType.Settings}, | ||||
|             {"suitcase", FolderType.Suitcase} | ||||
|         }; | ||||
| 
 | ||||
|         static SLUtil() | ||||
|         { | ||||
|  | @ -227,6 +278,20 @@ namespace OpenSim.Framework | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static AssetType SLAssetName2Type(string name) | ||||
|         { | ||||
|             if (name2Asset.TryGetValue(name, out AssetType type)) | ||||
|                 return type; | ||||
|             return AssetType.Unknown; | ||||
|         } | ||||
| 
 | ||||
|         public static FolderType SLInvName2Type(string name) | ||||
|         { | ||||
|             if (name2Inventory.TryGetValue(name, out FolderType type)) | ||||
|                 return type; | ||||
|             return FolderType.None; | ||||
|         } | ||||
| 
 | ||||
|         public static string SLAssetTypeToContentType(int assetType) | ||||
|         { | ||||
|             string contentType; | ||||
|  | @ -534,5 +599,188 @@ namespace OpenSim.Framework | |||
|         { | ||||
|             return readNotecard(rawInput).Replace("\r", "").Split('\n'); | ||||
|         } | ||||
| 
 | ||||
|         private static int skipcontrol(string data, int indx) | ||||
|         { | ||||
|             while(indx < data.Length) | ||||
|             { | ||||
|                 char c = data[indx]; | ||||
|                 switch(c) | ||||
|                 { | ||||
|                     case '\n': | ||||
|                     case '\t': | ||||
|                     case '{': | ||||
|                         ++indx; | ||||
|                         break; | ||||
|                     default: | ||||
|                         return indx; | ||||
|                 } | ||||
|             } | ||||
|             return -1; | ||||
|         } | ||||
| 
 | ||||
|         [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] | ||||
|         static int checkfield(string note, int start, string name, out int end) | ||||
|         { | ||||
|             end = -1; | ||||
|             int limit = note.Length - start; | ||||
|             if(limit > 64) | ||||
|                 limit = 64; | ||||
|             int indx = note.IndexOf(name, start, limit); | ||||
|             if (indx < 0) | ||||
|                 return -1; | ||||
|             indx += name.Length; | ||||
|             indx = skipcontrol(note, indx); | ||||
|             if (indx < 0) | ||||
|                 return -1; | ||||
|             end = note.IndexOfAny(seps, indx); | ||||
|             if (end < 0) | ||||
|                 return -1; | ||||
|             return indx; | ||||
|         } | ||||
| 
 | ||||
|         static char[] seps = new char[]{ '\t','\n'}; | ||||
|         public static InventoryItemBase GetEmbeddedItem(byte[] data, UUID itemID) | ||||
|         { | ||||
|             if(data == null || data.Length < 200) | ||||
|                 return null; | ||||
| 
 | ||||
|             string note = Util.UTF8.GetString(data); | ||||
|             if (String.IsNullOrWhiteSpace(note)) | ||||
|                 return null; | ||||
| 
 | ||||
|             int start = note.IndexOf(itemID.ToString()); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
| 
 | ||||
|             int end; | ||||
|             start = note.IndexOf("permissions", start, 100); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             start = checkfield(note, start, "base_mask", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
| 
 | ||||
|             if (!uint.TryParse(note.Substring(start, end - start), NumberStyles.HexNumber, Culture.NumberFormatInfo, out uint basemask)) | ||||
|                 return null; | ||||
| 
 | ||||
|             start = checkfield(note, end, "owner_mask", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
| 
 | ||||
|             if (!uint.TryParse(note.Substring(start, end - start), NumberStyles.HexNumber, Culture.NumberFormatInfo, out uint ownermask)) | ||||
|                 return null; | ||||
| 
 | ||||
|             start = checkfield(note, end, "group_mask", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!uint.TryParse(note.Substring(start, end - start), NumberStyles.HexNumber, Culture.NumberFormatInfo, out uint groupmask)) | ||||
|                 return null; | ||||
| 
 | ||||
|             start = checkfield(note, end, "everyone_mask", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!uint.TryParse(note.Substring(start, end - start), NumberStyles.HexNumber, Culture.NumberFormatInfo, out uint everyonemask)) | ||||
|                 return null; | ||||
| 
 | ||||
|             start = checkfield(note, end, "next_owner_mask", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!uint.TryParse(note.Substring(start, end - start), NumberStyles.HexNumber, Culture.NumberFormatInfo, out uint nextownermask)) | ||||
|                 return null; | ||||
| 
 | ||||
|             start = checkfield(note, end, "creator_id", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!UUID.TryParse(note.Substring(start, end - start), out UUID creatorID)) | ||||
|                 return null; | ||||
| 
 | ||||
|             start = checkfield(note, end, "owner_id", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!UUID.TryParse(note.Substring(start, end - start), out UUID ownerID)) | ||||
|                 return null; | ||||
| 
 | ||||
|             /* | ||||
|             start = checkfield(note, end, "last_owner_id", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!UUID.TryParse(note.Substring(start, end - start), out UUID lastownerID)) | ||||
|                 return null; | ||||
|             */ | ||||
| 
 | ||||
|             int limit = note.Length - end; | ||||
|             if (limit > 120) | ||||
|                 limit = 120; | ||||
|             end = note.IndexOf('}', end, limit); // last owner | ||||
|             start = checkfield(note, end, "asset_id", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!UUID.TryParse(note.Substring(start, end - start), out UUID assetID)) | ||||
|                 return null; | ||||
| 
 | ||||
|             start = checkfield(note, end, "type", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             string typestr = note.Substring(start, end - start); | ||||
|             AssetType assetType = SLAssetName2Type(typestr); | ||||
| 
 | ||||
|             start = checkfield(note, end, "inv_type", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             string inttypestr = note.Substring(start, end - start); | ||||
|             FolderType invType = SLInvName2Type(inttypestr); | ||||
| 
 | ||||
|             start = checkfield(note, end, "flags", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!uint.TryParse(note.Substring(start, end - start), NumberStyles.HexNumber, Culture.NumberFormatInfo, out uint flags)) | ||||
|                 return null; | ||||
| 
 | ||||
|             limit = note.Length - end; | ||||
|             if (limit > 120) | ||||
|                 limit = 120; | ||||
|             end = note.IndexOf('}', end, limit); // skip sale | ||||
|             start = checkfield(note, end, "name", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
| 
 | ||||
|             string name = note.Substring(start, end - start - 1); | ||||
| 
 | ||||
|             start = checkfield(note, end, "desc", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             string desc = note.Substring(start, end - start - 1); | ||||
|             /* | ||||
|             start = checkfield(note, end, "creation_date", out end); | ||||
|             if (start < 0) | ||||
|                 return null; | ||||
|             if (!int.TryParse(note.Substring(start, end - start), out int creationdate)) | ||||
|                 return null; | ||||
|             */ | ||||
| 
 | ||||
|             InventoryItemBase item = new InventoryItemBase(); | ||||
|             item.AssetID = assetID; | ||||
|             item.AssetType = (sbyte)assetType; | ||||
|             item.BasePermissions = basemask; | ||||
|             item.CreationDate = Util.UnixTimeSinceEpoch(); | ||||
|             item.CreatorData = ""; | ||||
|             item.CreatorId = creatorID.ToString(); | ||||
|             item.CurrentPermissions = ownermask; | ||||
|             item.Description = desc; | ||||
|             item.Flags = flags; | ||||
|             item.Folder = UUID.Zero; | ||||
|             item.GroupID = UUID.Zero; | ||||
|             item.GroupOwned = false; | ||||
|             item.GroupPermissions = groupmask; | ||||
|             item.InvType = (sbyte)invType; | ||||
|             item.Name = name; | ||||
|             item.NextPermissions = nextownermask; | ||||
|             item.Owner = ownerID; | ||||
|             item.SalePrice = 0; | ||||
|             item.SaleType = (byte)SaleType.Not; | ||||
|             item.ID = UUID.Random(); | ||||
|             return item; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1420,6 +1420,8 @@ namespace OpenSim.Region.ClientStack.Linden | |||
|                                              IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||||
|         { | ||||
|             InventoryItemBase copyItem = null; | ||||
|             IClientAPI client = null; | ||||
| 
 | ||||
|             try | ||||
|             { | ||||
|                 OSDMap content = (OSDMap)OSDParser.DeserializeLLSDXml(request); | ||||
|  | @ -1428,49 +1430,163 @@ namespace OpenSim.Region.ClientStack.Linden | |||
|                 UUID folderID = content["folder-id"].AsUUID(); | ||||
|                 UUID itemID = content["item-id"].AsUUID(); | ||||
| 
 | ||||
|                 UUID noteAssetID = UUID.Zero; | ||||
|                 //  m_log.InfoFormat("[CAPS]: CopyInventoryFromNotecard, FolderID:{0}, ItemID:{1}, NotecardID:{2}, ObjectID:{3}", folderID, itemID, notecardID, objectID); | ||||
| 
 | ||||
|                 m_Scene.TryGetClient(m_HostCapsObj.AgentID, out client); | ||||
| 
 | ||||
|                 if (objectID != UUID.Zero) | ||||
|                 { | ||||
|                     SceneObjectPart part = m_Scene.GetSceneObjectPart(objectID); | ||||
|                     if (part != null) | ||||
|                     { | ||||
| //                        TaskInventoryItem taskItem = part.Inventory.GetInventoryItem(notecardID); | ||||
|                     if(part == null) | ||||
|                         throw new Exception("find object with notecard item" + notecardID.ToString()); | ||||
| 
 | ||||
|                         if (!m_Scene.Permissions.CanCopyObjectInventory(notecardID, objectID, m_HostCapsObj.AgentID)) | ||||
|                             return CopyInventoryFromNotecardError(httpResponse); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 InventoryItemBase item = null; | ||||
|                 IClientAPI client = null; | ||||
| 
 | ||||
|                 m_Scene.TryGetClient(m_HostCapsObj.AgentID, out client); | ||||
|                 item = m_Scene.InventoryService.GetItem(m_HostCapsObj.AgentID, itemID); | ||||
|                 if (item != null) | ||||
|                 { | ||||
|                     string message; | ||||
|                     copyItem = m_Scene.GiveInventoryItem(m_HostCapsObj.AgentID, item.Owner, itemID, folderID, out message); | ||||
|                     if (copyItem != null && client != null) | ||||
|                     { | ||||
|                         m_log.InfoFormat("[CAPS]: CopyInventoryFromNotecard, ItemID:{0}, FolderID:{1}", copyItem.ID, copyItem.Folder); | ||||
|                         client.SendBulkUpdateInventory(copyItem); | ||||
|                     } | ||||
|                     TaskInventoryItem taskItem = part.Inventory.GetInventoryItem(notecardID); | ||||
|                     if(taskItem == null || taskItem.AssetID == UUID.Zero) | ||||
|                         throw new Exception("Failed to find notecard item" + notecardID.ToString()); | ||||
|                     noteAssetID = taskItem.AssetID; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     m_log.ErrorFormat("[CAPS]: CopyInventoryFromNotecard - Failed to retrieve item {0} from notecard {1}", itemID, notecardID); | ||||
|                     if (client != null) | ||||
|                         client.SendAlertMessage("Failed to retrieve item"); | ||||
|                     InventoryItemBase localitem = m_Scene.InventoryService.GetItem(m_HostCapsObj.AgentID, itemID); | ||||
|                     if (localitem != null) | ||||
|                     { | ||||
|                         string message; | ||||
|                         copyItem = m_Scene.GiveInventoryItem(m_HostCapsObj.AgentID, localitem.Owner, itemID, folderID, out message); | ||||
|                         if (copyItem == null) | ||||
|                             throw new Exception("Failed to find notecard item" + notecardID.ToString()); | ||||
| 
 | ||||
|                         m_log.InfoFormat("[CAPS]: CopyInventoryFromNotecard, ItemID:{0}, FolderID:{1}", copyItem.ID, copyItem.Folder); | ||||
|                         if (client != null) | ||||
|                             client.SendBulkUpdateInventory(copyItem); | ||||
|                         return ""; | ||||
|                     } | ||||
| 
 | ||||
|                     if (notecardID != UUID.Zero) | ||||
|                     { | ||||
|                         InventoryItemBase noteItem = m_Scene.InventoryService.GetItem(m_HostCapsObj.AgentID, notecardID); | ||||
|                         if (noteItem == null || noteItem.AssetID == UUID.Zero) | ||||
|                             throw new Exception("Failed to find notecard item" + notecardID.ToString()); | ||||
|                         noteAssetID = noteItem.AssetID; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 AssetBase noteAsset = m_Scene.AssetService.Get(noteAssetID.ToString()); | ||||
|                 if (noteAsset == null || noteAsset.Type != (sbyte)AssetType.Notecard) | ||||
|                     throw new Exception("Failed to find notecard asset" + notecardID.ToString()); | ||||
| 
 | ||||
|                 InventoryItemBase item = SLUtil.GetEmbeddedItem(noteAsset.Data, itemID); | ||||
|                 if(item == null) | ||||
|                     throw new Exception("Failed to open notecard asset" + notecardID.ToString()); | ||||
| 
 | ||||
|                 noteAsset = m_Scene.AssetService.Get(item.AssetID.ToString()); | ||||
|                 if (noteAsset == null) | ||||
|                     throw new Exception("Failed to find notecard " + notecardID.ToString() +" item "+ itemID.ToString() + " asset"); | ||||
| 
 | ||||
|                 if (!m_Scene.Permissions.CanTransferUserInventory(itemID, item.Owner, m_HostCapsObj.AgentID)) | ||||
|                     throw new Exception("Notecard item permissions check fail" + notecardID.ToString()); | ||||
| 
 | ||||
|                 if (!m_Scene.Permissions.BypassPermissions()) | ||||
|                 { | ||||
|                     if ((item.CurrentPermissions & (uint)PermissionMask.Transfer) == 0) | ||||
|                         throw new Exception("Notecard item permissions check fail" + notecardID.ToString()); | ||||
|                 } | ||||
| 
 | ||||
|                 if (m_Scene.Permissions.PropagatePermissions() && item.Owner != m_HostCapsObj.AgentID) | ||||
|                 { | ||||
|                     uint permsMask = ~((uint)PermissionMask.Copy | | ||||
|                                         (uint)PermissionMask.Transfer | | ||||
|                                         (uint)PermissionMask.Modify | | ||||
|                                         (uint)PermissionMask.Export); | ||||
| 
 | ||||
|                     uint nextPerms = permsMask | (item.NextPermissions & | ||||
|                                         ((uint)PermissionMask.Copy | | ||||
|                                         (uint)PermissionMask.Transfer | | ||||
|                                         (uint)PermissionMask.Modify)); | ||||
| 
 | ||||
|                     if (nextPerms == permsMask) | ||||
|                         nextPerms |= (uint)PermissionMask.Transfer; | ||||
| 
 | ||||
|                     uint basePerms = item.BasePermissions | | ||||
|                                     (uint)PermissionMask.Move; | ||||
|                     uint ownerPerms = item.CurrentPermissions; | ||||
| 
 | ||||
|                     uint foldedPerms = (item.CurrentPermissions & (uint)PermissionMask.FoldedMask) << (int)PermissionMask.FoldingShift; | ||||
|                     if (foldedPerms != 0 && item.InvType == (int)InventoryType.Object) | ||||
|                     { | ||||
|                         foldedPerms |= permsMask; | ||||
| 
 | ||||
|                         bool isRootMod = (item.CurrentPermissions & | ||||
|                                             (uint)PermissionMask.Modify) != 0 ? | ||||
|                                             true : false; | ||||
| 
 | ||||
|                         ownerPerms &= foldedPerms; | ||||
|                         basePerms &= foldedPerms; | ||||
| 
 | ||||
|                         if (isRootMod) | ||||
|                         { | ||||
|                             ownerPerms |= (uint)PermissionMask.Modify; | ||||
|                             basePerms |= (uint)PermissionMask.Modify; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     ownerPerms &= nextPerms; | ||||
|                     basePerms &= nextPerms; | ||||
|                     basePerms &= ~(uint)PermissionMask.FoldedMask; | ||||
|                     basePerms |= ((basePerms >> 13) & 7) | (((basePerms & (uint)PermissionMask.Export) != 0) ? (uint)PermissionMask.FoldedExport : 0); | ||||
|                     item.BasePermissions = basePerms; | ||||
|                     item.CurrentPermissions = ownerPerms; | ||||
|                     item.Flags |= (uint)InventoryItemFlags.ObjectSlamPerm; | ||||
|                     item.Flags &= ~(uint)(InventoryItemFlags.ObjectOverwriteBase | InventoryItemFlags.ObjectOverwriteOwner | InventoryItemFlags.ObjectOverwriteGroup | InventoryItemFlags.ObjectOverwriteEveryone | InventoryItemFlags.ObjectOverwriteNextOwner); | ||||
|                     item.NextPermissions = item.NextPermissions; | ||||
|                     item.EveryOnePermissions = item.EveryOnePermissions & nextPerms; | ||||
|                     item.GroupPermissions = 0; | ||||
|                 } | ||||
| 
 | ||||
|                 InventoryFolderBase folder = null; | ||||
|                 if (folderID != UUID.Zero) | ||||
|                     folder = m_Scene.InventoryService.GetFolder(m_HostCapsObj.AgentID, folderID); | ||||
| 
 | ||||
|                 if (folder == null && Enum.IsDefined(typeof(FolderType), (sbyte)item.AssetType)) | ||||
|                     folder = m_Scene.InventoryService.GetFolderForType(m_HostCapsObj.AgentID, (FolderType)item.AssetType); | ||||
| 
 | ||||
|                 if(folder == null) | ||||
|                     folder = m_Scene.InventoryService.GetRootFolder(m_HostCapsObj.AgentID); | ||||
| 
 | ||||
|                 if(folder == null) | ||||
|                     throw new Exception("Failed to find a folder for the notecard item" + notecardID.ToString()); | ||||
| 
 | ||||
|                 item.Folder = folder.ID; | ||||
| 
 | ||||
|                 UUID senderId = item.Owner; | ||||
|                 item.Owner = m_HostCapsObj.AgentID; | ||||
| 
 | ||||
|                 IInventoryAccessModule invAccess = m_Scene.RequestModuleInterface<IInventoryAccessModule>(); | ||||
|                 if (invAccess != null) | ||||
|                     invAccess.TransferInventoryAssets(item, senderId, m_HostCapsObj.AgentID); | ||||
| 
 | ||||
|                 if(!m_Scene.InventoryService.AddItem(item)) | ||||
|                     throw new Exception("Failed create the notecard item" + notecardID.ToString()); | ||||
| 
 | ||||
|                 m_log.InfoFormat("[CAPS]: CopyInventoryFromNotecard, ItemID:{0} FolderID:{1}", item.ID, item.Folder); | ||||
|                 if (client != null) | ||||
|                     client.SendBulkUpdateInventory(item); | ||||
|                 return ""; | ||||
|             } | ||||
|             catch (Exception e) | ||||
|             { | ||||
|                 m_log.ErrorFormat("[CAPS]: CopyInventoryFromNotecard : {0}", e.ToString()); | ||||
|                 m_log.ErrorFormat("[CAPS]: CopyInventoryFromNotecard : {0}", e.Message); | ||||
|                 copyItem = null; | ||||
|             } | ||||
| 
 | ||||
|             if(copyItem == null) | ||||
|             { | ||||
|                 if (client != null) | ||||
|                     client.SendAlertMessage("Failed to retrieve item"); | ||||
|                 return CopyInventoryFromNotecardError(httpResponse); | ||||
|             } | ||||
| 
 | ||||
|             return ""; | ||||
|         } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 UbitUmarov
						UbitUmarov