/// <summary> /// Return quota information by executing a <c>GetQuotaRoot</c> command passed /// as an argument. /// </summary> /// <param name="gqr"> /// A command for which the Query() has been called. /// </param> /// <param name="info"> /// A structure that receives the returned quota information. /// </param> /// <returns> /// <c>true</c> on success. /// </returns> /// <remarks> /// The quota root name is not always the same as the mailbox name, see /// the description of the return value. /// </remarks> public bool QuotaInfos(ZIMapCommand.GetQuotaRoot gqr, out QuotaInfo info) { info = new QuotaInfo(); if(factory == null || gqr == null) return false; // check result ... if(!gqr.CheckSuccess("")) return false; ZIMapCommand.GetQuotaRoot.Item[] quota = gqr.Quota; if(quota == null || quota.Length == 0) { MonitorInfo("QuotaInfo: nothing found"); return false; } // return results ... MonitorInfo("QuotaInfo: got " + quota.Length + " quota"); foreach(ZIMapCommand.GetQuotaRoot.Item item in quota) { if(item.Resource == "MESSAGE") { info.MessageUsage += item.Usage; info.MessageLimit += item.Limit; } else if(item.Resource == "STORAGE") { info.StorageUsage += item.Usage; info.StorageLimit += item.Limit; } } info.QuotaRoot = (gqr.Roots.Length > 0) ? gqr.Roots[gqr.Roots.Length-1] : ""; return true; }
/// <summary> /// A xtor to create a MailInfo from data returned by ZIMapCommand.Fetch /// </summary> public MailInfo(ZIMapCommand.Fetch.Item item) { Index = item.Index; UID = item.UID; Size = item.Size; Parts = item.Parts; Flags = item.Flags; Literal = item.Literal(0); UserData= null; }
/// <summary> /// An iterator to get the next command for with a pending request. /// </summary> /// <param name="current"> /// Returns the next command. /// </param> /// <returns> /// A value of <c>true</c> if the command is pending. /// </returns> /// <remarks> /// This iterator is typically used after all commands where submitted /// to fetch the results that are still pending. It should be called in /// a loop as long as it returns <c>true</c>. /// </remarks> public bool NextPending(ref ZIMapCommand.Generic current) { uint uidx = FindIndex(current); if(uidx == uint.MaxValue) parent.RaiseError(ZIMapException.Error.InvalidArgument); uint ucnt = (uint)commands.Length; while(ucnt > 0) { ucnt--; if(current != null) uidx++; if(uidx >= commands.Length) uidx = 0; current = commands[uidx]; if(current.IsPending) return true; } return false; }
private uint FindIndex(ZIMapCommand.Generic cmd) { if(commands == null) return uint.MaxValue; if(cmd == null) return 0; for(uint uidx=0; uidx < commands.Length; uidx++) if(object.ReferenceEquals(commands[uidx], cmd)) return uidx; return uint.MaxValue; }
// return running or completed command, never null (unless exception) private ZIMapCommand[] GetCommands(bool bCompleted) { if(GetCommands() == null) return null; // parent closed int cntCompleted = 0; int cntRunning = 0; // pass1: count the commands foreach(ZIMapCommand cmd in commands) switch(cmd.State) { case ZIMapCommand.CommandState.Running: cntRunning++; break; case ZIMapCommand.CommandState.Completed: case ZIMapCommand.CommandState.Failed: cntCompleted++; break; } // pass2: copy to array int cnt = bCompleted ? cntCompleted : cntRunning; ZIMapCommand[] arr = new ZIMapCommand[cnt]; if(cnt <= 0) return arr; int run = 0; foreach(ZIMapCommand cmd in commands) { switch(cmd.State) { case ZIMapCommand.CommandState.Running: if(bCompleted) continue; break; case ZIMapCommand.CommandState.Completed: case ZIMapCommand.CommandState.Failed: if(bCompleted) break; continue; default: continue; } arr[run++] = cmd; if(run >= cnt) break; } return arr; }
/// <summary> /// An iterator to get the next command for executing a request. /// </summary> /// <param name="current"> /// Returns the next command. On the initial call the value should /// be <c>null</c>. /// </param> /// <returns> /// The result of <see cref="ZIMapCommand.IsPending"/>. /// </returns> /// <remarks> /// If the return value is <c>true</c> the caller must fetch and read the result /// and call <see cref="ZIMapCommand.Reset"/> before the command can be reused. /// <para/> /// If the value of <paramref name="current"/> references a command whose state /// is <see cref="ZIMapCommand.CommandState.Queued"/> the queued command will /// be sent automatically. /// </remarks> /// <para /> /// Here a simple usage expample: /// <para /><example><code lang="C#"> /// uint ucnt = NNN; // number of messages /// uint usnd = 0; // send counter /// uint urcv = 0; // receive counter /// ZIMapCommand.Generic current = null; /// ZIMapFactory.Bulk bulk = app.Factory.CreateBulk("XXXX", 4, false); /// /// while(urcv < ucnt) /// { // step 1: queue request and check for response ... /// bool done = bulk.NextCommand(ref current); /// /// // step 2: check server reply for error ... /// if(done) /// { urcv++; /// if(!current.CheckSuccess("Command failed")) done = false; /// } /// /// // step 3: process data sent by server ... /// if(done) /// { /// } /// /// // step 4: create a new request /// if(usnd < ucnt) /// { current.Reset(); /// current.Queue(); /// usnd++; /// } /// } /// bulk.Dispose(); /// </code></example> public bool NextCommand(ref ZIMapCommand.Generic current) { if(current != null && current.State == ZIMapCommand.CommandState.Queued) current.Execute(false); uint uidx = FindIndex(current); if(uidx == uint.MaxValue) parent.RaiseError(ZIMapException.Error.InvalidArgument); if(current != null) uidx++; if(uidx >= commands.Length) uidx = 0; current = commands[uidx]; return current.State != ZIMapCommand.CommandState.Created; }
/// <summary> /// Attach a command to the factory, send request to server. /// </summary> /// <param name="command"> /// The command that is to be attached and/or sent. /// </param> /// <returns> /// Returns <c>true</c> on success. /// </returns> /// <remarks> /// Normally this method is not invoked directly, but it get's called /// automatically from <see cref="ZIMapCommand.Queue"/>. This is also /// the case for commands that were explicitly detached from the /// factory. /// <para /> /// If the command is already attached to the factory it will first be /// detached. Then the command will be attached to the factory and will /// be placed at the beginning of the commands list. If the command /// is not in the state <see cref="ZIMapCommand.CommandState.Queued"/> /// it will be queued (in other words: the request will be sent to /// the server). /// <para /> /// Usually commands get never explicitly detached from the factory that /// owns them until they are disposed, see <see cref="DetachCommand"/>. /// An error of type <see cref="ZIMapException.Error.InvalidArgument"/> /// is raised if the command was created by another factory instance. /// </remarks> public bool QueueCommand(ZIMapCommand command) { if(GetCommands() == null) return false; // parent closed if(command.Parent != Parent) // my command? { RaiseError(ZIMapException.Error.InvalidArgument, "wrong owner"); return false; } commands.Remove(command); // may fail: ok commands.Add(command); // move to start if(command.State == ZIMapCommand.CommandState.Queued) return true; // no change return command.Queue(); // tell the command }
/// <summary> /// Wait for running commands to complete. /// </summary> /// <param name="waitfor"> /// Stop waiting after the completion of the given command, can be <c>null</c>. /// </param> /// <returns> /// <c>true</c> on success. /// </returns> /// <remarks> /// This routine just waits for the results being receiced from the server. /// If does not send queued commands. For a <c>null</c> argument the /// routine waits for all running commands to complete. /// </remarks> public bool ExecuteRunning(ZIMapCommand waitfor) { while(HasRunningCommands) { ZIMapProtocol.ReceiveData rs; MonitorDebug("ExecuteRunning: waiting"); if( ((ZIMapConnection)Parent).ProtocolLayer.Receive(out rs) ) { Completed(rs); if(waitfor != null && waitfor.Tag == rs.Tag) break; } else { MonitorError("ExecuteRunning: receive failed"); return false; } } return true; }
/// <summary> /// Send all commands that are queued and optionally wait for the /// completion of all commands or of a specific command. /// </summary> /// <param name="waitfor"> /// Waits for all commands to complete if <c>null</c> -or- for a /// single cammand if a non-null object reference is passed. /// </param> /// <returns> /// <c>true</c> on success. /// </returns> /// <remarks>((ZIMapConnection)factory.Parent) /// Even if the server returns an error status for one or more commands /// this routine signals success. Only network level or protocal error /// will cause <c>false</c> to be returned. /// </remarks> public bool ExecuteCommands(ZIMapCommand waitfor) { if(!ExecuteCommands(false)) // send queued commands return false; return ExecuteRunning(waitfor); // Wait for results }
/// <summary> /// Release and cancel commands. /// </summary> /// <remarks> /// When a command instance is passed as 1st argument this command and all commands /// with a lower Tag number (e.g. that are older) get disposed unless they are still /// busy. The given command must have been sent (e.g. must have a non-zero Tag). /// This method is invoked by <see cref="ZIMapCommand.Dispose"/> method if the owning /// factorie's <see cref="EnableAutoDispose"/> property is set. /// <para /> /// Unsually one would like that only commands having the <see cref="ZIMapCommand.AutoDispose"/> /// property set are affected. A call with <paramref name="overrideAutoDispose"/> /// set <c>true</c> will include all commands. /// </remarks> public bool DisposeCommands(ZIMapCommand lastToDispose, bool overrideAutoDispose) { if(lastToDispose == null && overrideAutoDispose) MonitorDebug( "Disposing " + commands.Count + " command(s)"); uint tag = 0; if(lastToDispose != null) // get target tag { tag = lastToDispose.Tag; MonitorDebug("Disposing (auto): " + tag); if(tag == 0) { RaiseError(ZIMapException.Error.CommandState, "not sent, no tag"); return false; } } bool auto = autoDispose; try { autoDispose = false; // prevent recursion foreach(ZIMapCommand cmd in GetCommands(true)) { if(cmd.Tag > tag) continue; // younger than target if(!overrideAutoDispose && !cmd.AutoDispose) continue; // no autoDispose MonitorDebug("Disposing (auto): " + cmd.Tag); cmd.Dispose(); } } finally { autoDispose = auto; } return true; }
/// <summary> /// Remove a command from the commands list of the factory. /// </summary> /// <param name="command"> /// The command to be removed. /// </param> /// <returns> /// Returns <c>true</c> on success. /// </returns> /// <remarks> /// An error of type <see cref="ZIMapException.Error.InvalidArgument"/> /// is raised if the command was created by another factory instance. /// <para /> /// Usually commands get never explicitly detached from the factory that /// owns them until they are disposed. But an application that wants to keep /// a command alife for a long time (to access the returned data or to reuse /// it later) should eventually detach the command from the factory. /// The <see cref="QueueCommand"/> method will (re-)attach it automatically /// when the command is reused. This also happens when /// <see cref="ZIMapCommand.Queue"/> is called. /// </remarks> public bool DetachCommand(ZIMapCommand command) { if(GetCommands() == null) return false; // parent closed if(command.Parent != Parent) // my command? { RaiseError(ZIMapException.Error.InvalidArgument, "wrong owner"); return false; } if(!commands.Remove(command)) return false; // not in list if(command.State != ZIMapCommand.CommandState.Disposed) command.Reset(); // tell the command return true; }