corrade-vassal – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 1 | using System; |
2 | using System.Collections.Generic; |
||
3 | using System.IO; |
||
4 | using System.Text; |
||
5 | using System.Threading; |
||
6 | using System.ComponentModel; |
||
7 | using System.Xml; |
||
8 | using System.Xml.Serialization; |
||
9 | using OpenMetaverse; |
||
10 | using OpenMetaverse.Packets; |
||
11 | using OpenMetaverse.TestClient; |
||
12 | using OpenMetaverse.Assets; |
||
13 | |||
14 | namespace OpenMetaverse.TestClient |
||
15 | { |
||
16 | public class QueuedDownloadInfo |
||
17 | { |
||
18 | public UUID AssetID; |
||
19 | public UUID ItemID; |
||
20 | public UUID TaskID; |
||
21 | public UUID OwnerID; |
||
22 | public AssetType Type; |
||
23 | public string FileName; |
||
24 | public DateTime WhenRequested; |
||
25 | public bool IsRequested; |
||
26 | |||
27 | public QueuedDownloadInfo(string file, UUID asset, UUID item, UUID task, UUID owner, AssetType type) |
||
28 | { |
||
29 | FileName = file; |
||
30 | AssetID = asset; |
||
31 | ItemID = item; |
||
32 | TaskID = task; |
||
33 | OwnerID = owner; |
||
34 | Type = type; |
||
35 | WhenRequested = DateTime.Now; |
||
36 | IsRequested = false; |
||
37 | } |
||
38 | } |
||
39 | |||
40 | public class BackupCommand : Command |
||
41 | { |
||
42 | /// <summary>Maximum number of transfer requests to send to the server</summary> |
||
43 | private const int MAX_TRANSFERS = 10; |
||
44 | |||
45 | // all items here, fed by the inventory walking thread |
||
46 | private Queue<QueuedDownloadInfo> PendingDownloads = new Queue<QueuedDownloadInfo>(); |
||
47 | |||
48 | // items sent to the server here |
||
49 | private List<QueuedDownloadInfo> CurrentDownloads = new List<QueuedDownloadInfo>(MAX_TRANSFERS); |
||
50 | |||
51 | // background workers |
||
52 | private BackgroundWorker BackupWorker; |
||
53 | private BackgroundWorker QueueWorker; |
||
54 | |||
55 | // some stats |
||
56 | private int TextItemsFound; |
||
57 | private int TextItemsTransferred; |
||
58 | private int TextItemErrors; |
||
59 | |||
60 | #region Properties |
||
61 | |||
62 | /// <summary> |
||
63 | /// true if either of the background threads is running |
||
64 | /// </summary> |
||
65 | private bool BackgroundBackupRunning |
||
66 | { |
||
67 | get { return InventoryWalkerRunning || QueueRunnerRunning; } |
||
68 | } |
||
69 | |||
70 | /// <summary> |
||
71 | /// true if the thread walking inventory is running |
||
72 | /// </summary> |
||
73 | private bool InventoryWalkerRunning |
||
74 | { |
||
75 | get { return BackupWorker != null; } |
||
76 | } |
||
77 | |||
78 | /// <summary> |
||
79 | /// true if the thread feeding the queue to the server is running |
||
80 | /// </summary> |
||
81 | private bool QueueRunnerRunning |
||
82 | { |
||
83 | get { return QueueWorker != null; } |
||
84 | } |
||
85 | |||
86 | /// <summary> |
||
87 | /// returns a string summarizing activity |
||
88 | /// </summary> |
||
89 | /// <returns></returns> |
||
90 | private string BackgroundBackupStatus |
||
91 | { |
||
92 | get |
||
93 | { |
||
94 | StringBuilder sbResult = new StringBuilder(); |
||
95 | sbResult.AppendFormat("{0} is {1} running.", Name, BoolToNot(BackgroundBackupRunning)); |
||
96 | if (TextItemErrors != 0 || TextItemsFound != 0 || TextItemsTransferred != 0) |
||
97 | { |
||
98 | sbResult.AppendFormat("\r\n{0} : Inventory walker ( {1} running ) has found {2} items.", |
||
99 | Name, BoolToNot(InventoryWalkerRunning), TextItemsFound); |
||
100 | sbResult.AppendFormat("\r\n{0} : Server Transfers ( {1} running ) has transferred {2} items with {3} errors.", |
||
101 | Name, BoolToNot(QueueRunnerRunning), TextItemsTransferred, TextItemErrors); |
||
102 | sbResult.AppendFormat("\r\n{0} : {1} items in Queue, {2} items requested from server.", |
||
103 | Name, PendingDownloads.Count, CurrentDownloads.Count); |
||
104 | } |
||
105 | return sbResult.ToString(); |
||
106 | } |
||
107 | } |
||
108 | |||
109 | #endregion Properties |
||
110 | |||
111 | public BackupCommand(TestClient testClient) |
||
112 | { |
||
113 | Name = "backuptext"; |
||
114 | Description = "Backup inventory to a folder on your hard drive. Usage: " + Name + " [to <directory>] | [abort] | [status]"; |
||
115 | } |
||
116 | |||
117 | public override string Execute(string[] args, UUID fromAgentID) |
||
118 | { |
||
119 | |||
120 | if (args.Length == 1 && args[0] == "status") |
||
121 | { |
||
122 | return BackgroundBackupStatus; |
||
123 | } |
||
124 | else if (args.Length == 1 && args[0] == "abort") |
||
125 | { |
||
126 | if (!BackgroundBackupRunning) |
||
127 | return BackgroundBackupStatus; |
||
128 | |||
129 | BackupWorker.CancelAsync(); |
||
130 | QueueWorker.CancelAsync(); |
||
131 | |||
132 | Thread.Sleep(500); |
||
133 | |||
134 | // check status |
||
135 | return BackgroundBackupStatus; |
||
136 | } |
||
137 | else if (args.Length != 2) |
||
138 | { |
||
139 | return "Usage: " + Name + " [to <directory>] | [abort] | [status]"; |
||
140 | } |
||
141 | else if (BackgroundBackupRunning) |
||
142 | { |
||
143 | return BackgroundBackupStatus; |
||
144 | } |
||
145 | |||
146 | QueueWorker = new BackgroundWorker(); |
||
147 | QueueWorker.WorkerSupportsCancellation = true; |
||
148 | QueueWorker.DoWork += new DoWorkEventHandler(bwQueueRunner_DoWork); |
||
149 | QueueWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwQueueRunner_RunWorkerCompleted); |
||
150 | |||
151 | QueueWorker.RunWorkerAsync(); |
||
152 | |||
153 | BackupWorker = new BackgroundWorker(); |
||
154 | BackupWorker.WorkerSupportsCancellation = true; |
||
155 | BackupWorker.DoWork += new DoWorkEventHandler(bwBackup_DoWork); |
||
156 | BackupWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwBackup_RunWorkerCompleted); |
||
157 | |||
158 | BackupWorker.RunWorkerAsync(args); |
||
159 | return "Started background operations."; |
||
160 | } |
||
161 | |||
162 | void bwQueueRunner_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) |
||
163 | { |
||
164 | QueueWorker = null; |
||
165 | Console.WriteLine(BackgroundBackupStatus); |
||
166 | } |
||
167 | |||
168 | void bwQueueRunner_DoWork(object sender, DoWorkEventArgs e) |
||
169 | { |
||
170 | TextItemErrors = TextItemsTransferred = 0; |
||
171 | |||
172 | while (QueueWorker.CancellationPending == false) |
||
173 | { |
||
174 | // have any timed out? |
||
175 | if (CurrentDownloads.Count > 0) |
||
176 | { |
||
177 | foreach (QueuedDownloadInfo qdi in CurrentDownloads) |
||
178 | { |
||
179 | if ((qdi.WhenRequested + TimeSpan.FromSeconds(60)) < DateTime.Now) |
||
180 | { |
||
181 | Logger.DebugLog(Name + ": timeout on asset " + qdi.AssetID.ToString(), Client); |
||
182 | // submit request again |
||
183 | Client.Assets.RequestInventoryAsset( |
||
184 | qdi.AssetID, qdi.ItemID, qdi.TaskID, qdi.OwnerID, qdi.Type, true, Assets_OnAssetReceived); |
||
185 | qdi.WhenRequested = DateTime.Now; |
||
186 | qdi.IsRequested = true; |
||
187 | } |
||
188 | } |
||
189 | } |
||
190 | |||
191 | if (PendingDownloads.Count != 0) |
||
192 | { |
||
193 | // room in the server queue? |
||
194 | if (CurrentDownloads.Count < MAX_TRANSFERS) |
||
195 | { |
||
196 | // yes |
||
197 | QueuedDownloadInfo qdi = PendingDownloads.Dequeue(); |
||
198 | qdi.WhenRequested = DateTime.Now; |
||
199 | qdi.IsRequested = true; |
||
200 | Client.Assets.RequestInventoryAsset( |
||
201 | qdi.AssetID, qdi.ItemID, qdi.TaskID, qdi.OwnerID, qdi.Type, true, Assets_OnAssetReceived); |
||
202 | |||
203 | lock (CurrentDownloads) CurrentDownloads.Add(qdi); |
||
204 | } |
||
205 | } |
||
206 | |||
207 | if (CurrentDownloads.Count == 0 && PendingDownloads.Count == 0 && BackupWorker == null) |
||
208 | { |
||
209 | Logger.DebugLog(Name + ": both transfer queues empty AND inventory walking thread is done", Client); |
||
210 | return; |
||
211 | } |
||
212 | |||
213 | Thread.Sleep(100); |
||
214 | } |
||
215 | } |
||
216 | |||
217 | void bwBackup_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) |
||
218 | { |
||
219 | Console.WriteLine(Name + ": Inventory walking thread done."); |
||
220 | BackupWorker = null; |
||
221 | } |
||
222 | |||
223 | private void bwBackup_DoWork(object sender, DoWorkEventArgs e) |
||
224 | { |
||
225 | string[] args; |
||
226 | |||
227 | TextItemsFound = 0; |
||
228 | |||
229 | args = (string[])e.Argument; |
||
230 | |||
231 | lock (CurrentDownloads) CurrentDownloads.Clear(); |
||
232 | |||
233 | // FIXME: |
||
234 | //Client.Inventory.RequestFolderContents(Client.Inventory.Store.RootFolder.UUID, Client.Self.AgentID, |
||
235 | // true, true, false, InventorySortOrder.ByName); |
||
236 | |||
237 | DirectoryInfo di = new DirectoryInfo(args[1]); |
||
238 | |||
239 | // recurse on the root folder into the entire inventory |
||
240 | BackupFolder(Client.Inventory.Store.RootNode, di.FullName); |
||
241 | } |
||
242 | |||
243 | /// <summary> |
||
244 | /// BackupFolder - recurse through the inventory nodes sending scripts and notecards to the transfer queue |
||
245 | /// </summary> |
||
246 | /// <param name="folder">The current leaf in the inventory tree</param> |
||
247 | /// <param name="sPathSoFar">path so far, in the form @"c:\here" -- this needs to be "clean" for the current filesystem</param> |
||
248 | private void BackupFolder(InventoryNode folder, string sPathSoFar) |
||
249 | { |
||
250 | |||
251 | // FIXME: |
||
252 | //Client.Inventory.RequestFolderContents(folder.Data.UUID, Client.Self.AgentID, true, true, false, |
||
253 | // InventorySortOrder.ByName); |
||
254 | |||
255 | // first scan this folder for text |
||
256 | foreach (InventoryNode iNode in folder.Nodes.Values) |
||
257 | { |
||
258 | if (BackupWorker.CancellationPending) |
||
259 | return; |
||
260 | if (iNode.Data is OpenMetaverse.InventoryItem) |
||
261 | { |
||
262 | InventoryItem ii = iNode.Data as InventoryItem; |
||
263 | if (ii.AssetType == AssetType.LSLText || ii.AssetType == AssetType.Notecard) |
||
264 | { |
||
265 | // check permissions on scripts |
||
266 | if (ii.AssetType == AssetType.LSLText) |
||
267 | { |
||
268 | if ((ii.Permissions.OwnerMask & PermissionMask.Modify) == PermissionMask.None) |
||
269 | { |
||
270 | // skip this one |
||
271 | continue; |
||
272 | } |
||
273 | } |
||
274 | |||
275 | string sExtension = (ii.AssetType == AssetType.LSLText) ? ".lsl" : ".txt"; |
||
276 | // make the output file |
||
277 | string sPath = sPathSoFar + @"\" + MakeValid(ii.Name.Trim()) + sExtension; |
||
278 | |||
279 | // create the new qdi |
||
280 | QueuedDownloadInfo qdi = new QueuedDownloadInfo(sPath, ii.AssetUUID, iNode.Data.UUID, UUID.Zero, |
||
281 | Client.Self.AgentID, ii.AssetType); |
||
282 | |||
283 | // add it to the queue |
||
284 | lock (PendingDownloads) |
||
285 | { |
||
286 | TextItemsFound++; |
||
287 | PendingDownloads.Enqueue(qdi); |
||
288 | } |
||
289 | } |
||
290 | } |
||
291 | } |
||
292 | |||
293 | // now run any subfolders |
||
294 | foreach (InventoryNode i in folder.Nodes.Values) |
||
295 | { |
||
296 | if (BackupWorker.CancellationPending) |
||
297 | return; |
||
298 | else if (i.Data is OpenMetaverse.InventoryFolder) |
||
299 | BackupFolder(i, sPathSoFar + @"\" + MakeValid(i.Data.Name.Trim())); |
||
300 | } |
||
301 | } |
||
302 | |||
303 | private string MakeValid(string path) |
||
304 | { |
||
305 | // FIXME: We need to strip illegal characters out |
||
306 | return path.Trim().Replace('"', '\''); |
||
307 | } |
||
308 | |||
309 | private void Assets_OnAssetReceived(AssetDownload asset, Asset blah) |
||
310 | { |
||
311 | lock (CurrentDownloads) |
||
312 | { |
||
313 | // see if we have this in our transfer list |
||
314 | QueuedDownloadInfo r = CurrentDownloads.Find(delegate(QueuedDownloadInfo q) |
||
315 | { |
||
316 | return q.AssetID == asset.AssetID; |
||
317 | }); |
||
318 | |||
319 | if (r != null && r.AssetID == asset.AssetID) |
||
320 | { |
||
321 | if (asset.Success) |
||
322 | { |
||
323 | // create the directory to put this in |
||
324 | Directory.CreateDirectory(Path.GetDirectoryName(r.FileName)); |
||
325 | |||
326 | // write out the file |
||
327 | File.WriteAllBytes(r.FileName, asset.AssetData); |
||
328 | Logger.DebugLog(Name + " Wrote: " + r.FileName, Client); |
||
329 | TextItemsTransferred++; |
||
330 | } |
||
331 | else |
||
332 | { |
||
333 | TextItemErrors++; |
||
334 | Console.WriteLine("{0}: Download of asset {1} ({2}) failed with status {3}", Name, r.FileName, |
||
335 | r.AssetID.ToString(), asset.Status.ToString()); |
||
336 | } |
||
337 | |||
338 | // remove the entry |
||
339 | CurrentDownloads.Remove(r); |
||
340 | } |
||
341 | } |
||
342 | } |
||
343 | |||
344 | /// <summary> |
||
345 | /// returns blank or "not" if false |
||
346 | /// </summary> |
||
347 | /// <param name="b"></param> |
||
348 | /// <returns></returns> |
||
349 | private static string BoolToNot(bool b) |
||
350 | { |
||
351 | return b ? String.Empty : "not"; |
||
352 | } |
||
353 | } |
||
354 | } |