001    package com.skype.api;
002    
003    import java.io.IOException;
004    import java.util.*;
005    import com.skype.ipc.*;
006    /**
007     * Transfer in this context refers to transferring (sending/receiving) files among Skype Contacts, not transferring a call to another Skype or PSTN Contact. This class includes file transfer-specific properties and methods, such as FILESIZE, BYTESPERSECOND, Pause and Resume. Recipients of these file transfers must explicitly accept (or decline) any incoming transfer. Transfer instances represent files being sent and received within a Conversation context. Each Transfer instance represents a single file transfer - if a conversation has multiple remote participants, a separate Transfer instance must be instantiated for each remote participant (a Transfer instance is not instantiated for the sender). <br><br>Transfer instances cannot be instantiated directly. Instead, you initiate a file transfer by invoking Conversation.PostFiles. This instantiates a Message instance of type POSTED_FILES, which is added to the Conversation for all the participants (including the sender). The Transfer instance is associated with <br>this Message instance, and the Message.P_BODY_XML looks like this: <br><br>@code <br>Some text<files alt=""><file size="2336020" index="0">test.zip</file></files>  <br></CODE> <br><br>To put it another way, the object chain goes like this: <br>@code <br>Conversation->Message->Transfer  <br></CODE> <br><br>The first part of the message (before the files section) comes from the Conversation.PostFiles body argument. For each file in the message, a file section is provided with three fields: <br> - file size in bytes <br> - index - more or less arbitrary order in which the transfers should be displayed in the UI <br> - file name. <br><br>For practical purposes, the Message.P_BODY_XML property is not all that useful in this context. The Transfer instances, however, contain the state and progress feedback for your UI. You retrieve these Transfer instances using Message.GetTransfers method. Since the sender can post multiple files to <br>a Conversation using the same Message, Message:GetTransfers retrieves a list of Transfer instances - one per file per recipient. <br><br>You can determine the direction of particular Transfer instance by querying Transfer.P_TYPE (INCOMING/OUTGOING).  <br><br>You can implement a progress indicator by monitoring Transfer.P_BYTESTRANSFERRED. Note that when testing this on your local network, you will most likely catch these property change events at the beginning and the end of the transfer only - which does not look like too useful. However, for non-local network transfers where the transfer speeds are in the neighborhood of 200-300 KB per second, you should consider implementing progress feedback as being mandatory and expect to catch multiple property change events for all but the smallest files. <br><br>Another property of interest is Transfer.P_STATUS. The normal transfer status sequence during successful outgoing transfer is this: <br> - TRANSFER STATUS -> NEW <br> - TRANSFER STATUS -> WAITING_FOR_ACCEPT <br> - TRANSFER STATUS -> CONNECTING <br> - TRANSFER STATUS -> TRANSFERRING <br> - TRANSFER STATUS -> CONNECTING <br> - TRANSFER STATUS -> COMPLETED <br><br>The list of all terminal Transfer statuses is: <br> - COMPLETED <br> - FAILED <br> - CANCELLED <br> - CANCELLED_BY_REMOTE <br><br>In-progress transfers can be canceled with Transfer.Cancel and paused/resumed with Transfer.Pause and Transfer.Resume. For transfers that complete with a status of FAILED, your UI should provide feedback based on the value of Transfer.P_FAILUREREASON. <br><br>Incoming transfers, once accepted, overwrite existing files with the same name. Before accepting an incoming file transfer, <br>your UI should prompt the recipient to: <br> - accept or decline the file <br> - if accepted, specify the directory of here to save the file (with a pre-filled default) <br> - if accepted and a file having the same name already exists at the specified destination, your UI should prompt for confirmation to overwrite and provide a way to alter the file name before accepting it <br><br>Similarly, your UI should verify the existence of outgoing files prior to invoking Conversation.PostFiles. <br><br>Note that you should provide both Conversation.PostFiles and Transfer.Accept methods fully-qualified paths. Otherwise, the paths will be assumed to be relative to the path of SkypeKit runtime, since the methods are actually executed in the runtime context. <br>
008     */
009    
010    
011    public class Transfer extends SkypeObject {
012    
013    
014            public interface TransferListener {
015                    /** This event gets called when there are changes to Transfer properties defined in Transfer.PROPERTY  */
016                    public void OnPropertyChange(SkypeObject obj, PROPERTY prop, Object value);
017                    
018            }
019            
020            public Transfer(int oid, Skype skype) {
021                    super(oid,skype);
022            }
023            
024            private static final int MODULE_ID = 6;
025            
026            public static final int moduleID() {
027                    return MODULE_ID;
028            }
029            
030            /** Properties of the Transfer class */
031            public enum PROPERTY {
032            
033                    /** INCOMING / OUTGOING <br>, type: TYPE */
034                    type(80),
035                    
036                    /** Skype Name of the remote party of the file transfer. If a file is posted in a conversation with more than one participant, Transfer objects are created for each of them - so a transfer is always to one single remote target. <br>, type: String */
037                    partner_handle(81),
038                    
039                    /** Display name of the remote participant. <br>, type: String */
040                    partner_dispname(82),
041                    
042                    /** Current state of the transfer <br>, type: STATUS */
043                    status(83),
044                    
045                    /** Set whenever P_STATUS transitions to FAILED. <br>, type: FAILUREREASON */
046                    failurereason(84),
047                    
048                    /** UNIX timestamp of when this Transfer instance was instantiated, not when the transfer process actually started (was accepted from receiver side). Do not use this property when calculate the data transfer speed! Instead, monitor changes to P_BYTESPERSECOND. <br>, type: int */
049                    starttime(85),
050                    
051                    /** UNIX timestamp of when this Transfer COMPLETED or FAILED. This property is never set if the receiving side (local or remote) canceled the transfer. <br>, type: int */
052                    finishtime(86),
053                    
054                    /** The path -and- filename of the file being transfered (typically fully qualified). For the receiver, SkypeKit sets this property upon acceptance of the incoming transfer. If not fully qualified, the path is assumed to be relative to the path of the SkypeKit runtime. <br>, type: String */
055                    filepath(87),
056                    
057                    /** The filename -only- of the file being transfered. The receiver side can use this property to pre-populate relevant UI components while prompting the user to accept the incoming transfer. <br>, type: String */
058                    filename(88),
059                    
060                    /** The size of the file being transferred in bytes. Depending on the magnitude of this value, your UI might want to display the size in terms of kilobytes or even megabytes. <br>, type: String */
061                    filesize(89),
062                    
063                    /** The number of bytes already transferred. Calculate the percentage of the file transferred so far as: <br>@code <br>P_BYTESTRANSFERRED / (P_FILESIZE / 100);  <br></CODE> <br><br>Use float variables to avoid problems with files smaller than 100 bytes! <br>, type: String */
064                    bytestransferred(90),
065                    
066                    /** Current data transfer speed in bytes per second. Typically, your UI will want to display this value as kilobytes per second (KBps). <br>, type: int */
067                    bytespersecond(91),
068                    
069                    /** The "global ID" of this Transfer's associated Message instance. GUIDs are shared across Skype client instances and across all users that can see this Message. <br>, type: byte[] */
070                    chatmsg_guid(92),
071                    
072                    /** A more or less arbitrary index for ordering multiple file transfers within the UI. <br>, type: int */
073                    chatmsg_index(93),
074                    
075                    /** The "global ID" of this Transfer's associated Conversation (as chained through its associated Message). GUIDs are shared across Skype client instances and across all users that can see this Conversation. <br><br>Note that currently SkypeKit sets this property for INCOMING file transfers only and returns 0 (zero) for all sending side transfers. This is a known bug. <br>, type: Conversation */
076                    convo_id(98);
077                    
078                    private static final Map<Integer,PROPERTY> lookup = new HashMap<Integer,PROPERTY>();
079                    
080                    static {
081                            for(PROPERTY s : EnumSet.allOf(PROPERTY.class))
082                                    lookup.put(s.getId(), s);
083                    }
084                    
085                    private final int id;
086                    
087                    private PROPERTY(int value) {
088                            this.id = value;
089                    }
090                    
091                    public int getId() { return id; }
092                    
093                    public static PROPERTY get(int code) {
094                            return lookup.get(code);
095                    }
096                    
097                    public static PROPERTY fromString(String s) {
098                            for (PROPERTY p : lookup.values()) {
099                                    if (p.toString() == s) {
100                                            return p;
101                                    }
102                            }
103                            return null;
104                    }
105            }
106            
107            public Object GetPropertyAsEnum(int propid) {
108                    return PROPERTY.get(propid);
109            }
110            
111            public String GetStrProperty(PROPERTY prop) {
112                    //check in propcache if so then return
113                    if (mPropCache.containsKey(new Integer(prop.id))){
114                            String value =  (String)mPropCache.get(prop.id);
115                            if (value != null && !(value.length() == 0) ){
116                                    return value;
117                            }
118                    }
119                    //else get from skypekit...
120                    GetPropertyRequest request = new GetPropertyRequest(6, mObjectId, prop.id);
121                    
122                    String string = null;
123                    GetPropertyResponse r = skype.GetProperty(request);
124                    if (r != null){
125                            string = r.GetAsString();
126                    }
127                    
128                    if (string != null)
129                    {
130                            mPropCache.put(new Integer(prop.id), string);
131                    }
132                    return string;
133            }
134            
135            public int GetIntProperty(PROPERTY prop) {
136                    //check in propcache if so then return
137                    if (mPropCache.containsKey(new Integer(prop.id))){
138                            int value = ((Integer)mPropCache.get(prop.id)).intValue();
139                            if (value != 0){
140                                    return value;
141                            }
142                    }
143                    //else get from skypekit...
144                    GetPropertyRequest request = new GetPropertyRequest(moduleID(), mObjectId, prop.id);
145                    
146                    Integer integer = null;
147                    GetPropertyResponse r = skype.GetProperty(request);
148                    if (r != null){
149                            integer  = r.GetAsInt();
150                    }
151                    
152                    if (integer != null)
153                    {
154                            mPropCache.put(new Integer(prop.id), integer);
155                            return integer.intValue();
156                    }
157                    else
158                    {
159                            return 0;
160                    }
161            }
162            
163            public boolean GetBooleanProperty(PROPERTY prop) {
164                    //check in propcache if so then return
165                    if (mPropCache.containsKey(new Integer(prop.id))){
166                            return ((Boolean)mPropCache.get(prop.id)).booleanValue();
167                    }
168                    //else get from skypekit...
169                    GetPropertyRequest request = new GetPropertyRequest(moduleID(), mObjectId, prop.id);
170                    
171                    Boolean boolResp = null;
172                    GetPropertyResponse r = skype.GetProperty(request);
173                    if (r != null){
174                            boolResp  = r.GetAsBoolean();
175                    }
176                    
177                    if (boolResp != null)
178                    {
179                            mPropCache.put(new Integer(prop.id), boolResp);
180                            return boolResp.booleanValue();
181                    }
182                    else
183                    {
184                            return false;
185                    }
186            }
187            
188            public byte [] GetBinProperty(PROPERTY prop) {
189                    //get from skypekit...
190                    GetPropertyRequest request = new GetPropertyRequest(6, mObjectId, prop.id);
191                    
192                    byte [] data = null;
193                    GetPropertyResponse r = skype.GetProperty(request);
194                    if (r != null) {
195                            data = r.GetAsBinary();
196                    }
197                    return data;
198            }
199            
200            /**
201             */
202            public enum TYPE {
203            
204                    /** */
205                    INCOMING(1),
206                    
207                    /** */
208                    OUTGOING(2);
209                    
210                    private static final Map<Integer,TYPE> lookup = new HashMap<Integer,TYPE>();
211                    
212                    static {
213                            for(TYPE s : EnumSet.allOf(TYPE.class))
214                                    lookup.put(s.getId(), s);
215                    }
216                    
217                    private final int id;
218                    
219                    private TYPE(int value) {
220                            this.id = value;
221                    }
222                    
223                    public int getId() { return id; }
224                    
225                    public static TYPE get(int code) {
226                            return lookup.get(code);
227                    }
228                    
229                    public static TYPE fromString(String s) {
230                            for (TYPE p : lookup.values()) {
231                                    if (p.toString() == s) {
232                                            return p;
233                                    }
234                            }
235                            return null;
236                    }
237            }
238            
239            /**
240            Recognized values for the P_STATUS property. Reflects current state of this Transfer. <br> */
241            public enum STATUS {
242            
243                    /** The file has either not been posted (sent) (OUTGOING), or not accepted (received) (INCOMING). <br>*/
244                    NEW(0),
245                    
246                    /** A temporary state that transitions either into TRANSFERRING (relayed or direct) or to FAILED. For unknown reasons, outgoing transfers tend go into this state twice - immediately before the actual data transfer starts and immediately after it ends. <br>*/
247                    CONNECTING(1),
248                    
249                    /** The files have been posted but the recipient has not yet accepted (or has declined) the transfer. <br>*/
250                    WAITING_FOR_ACCEPT(2),
251                    
252                    /** The transfer has been accepted and file data is being sent/received. Periodic updates of P_BYTESTRANSFERRED property should occur. <br>*/
253                    TRANSFERRING(3),
254                    
255                    /** The transfer has been accepted and file data is being sent/received but is going over at least one relay. Since relayed transfers tend to be significantly slower than direct transfers, you might want to differentiate the two in your UI and notify the user that relayed transfer typically take significantly longer to finish. <br>*/
256                    TRANSFERRING_OVER_RELAY(4),
257                    
258                    /** The local user (either the sender or a receiver) has paused the transfer. <br>*/
259                    PAUSED(5),
260                    
261                    /** A remote user has paused the transfer. For senders, a receiver has paused the transfer; for receivers, the sender has paused the transfer. <br>*/
262                    REMOTELY_PAUSED(6),
263                    
264                    /** Local side (either sender or receiver) has canceled the transfer. This is a final state of the STATE property. <br>*/
265                    CANCELLED(7),
266                    
267                    /** File transfer has completed. This is a terminal state. <br>*/
268                    COMPLETED(8),
269                    
270                    /** File transfer has failed. This is a terminal state. UI should provide feedback, based on value of P_FAILUREREASON. <br>*/
271                    FAILED(9),
272                    
273                    /** Transfer whose existence has been hinted by corresponding chat message, but which is yet to arrive. <br>*/
274                    PLACEHOLDER(10),
275                    
276                    /** Outgoing transfer object from another instance of the same account as current local login, running on another system. Hinted through chat message - only implies an offer was made; not necessarily accepted, failed, or completed.  <br>*/
277                    OFFER_FROM_OTHER_INSTANCE(11),
278                    
279                    /** Remote side (either sender or receiver) has canceled the transfer. This is a final state of the STATE property. <br>*/
280                    CANCELLED_BY_REMOTE(12);
281                    
282                    private static final Map<Integer,STATUS> lookup = new HashMap<Integer,STATUS>();
283                    
284                    static {
285                            for(STATUS s : EnumSet.allOf(STATUS.class))
286                                    lookup.put(s.getId(), s);
287                    }
288                    
289                    private final int id;
290                    
291                    private STATUS(int value) {
292                            this.id = value;
293                    }
294                    
295                    public int getId() { return id; }
296                    
297                    public static STATUS get(int code) {
298                            return lookup.get(code);
299                    }
300                    
301                    public static STATUS fromString(String s) {
302                            for (STATUS p : lookup.values()) {
303                                    if (p.toString() == s) {
304                                            return p;
305                                    }
306                            }
307                            return null;
308                    }
309            }
310            
311            /**
312             */
313            public enum FAILUREREASON {
314            
315                    /** */
316                    SENDER_NOT_AUTHORISED(1),
317                    
318                    /** */
319                    REMOTELY_CANCELLED(2),
320                    
321                    /** */
322                    FAILED_READ(3),
323                    
324                    /** */
325                    FAILED_REMOTE_READ(4),
326                    
327                    /** */
328                    FAILED_WRITE(5),
329                    
330                    /** */
331                    FAILED_REMOTE_WRITE(6),
332                    
333                    /** */
334                    REMOTE_DOES_NOT_SUPPORT_FT(7),
335                    
336                    /** */
337                    REMOTE_OFFLINE_FOR_TOO_LONG(8),
338                    
339                    /** */
340                    TOO_MANY_PARALLEL(9),
341                    
342                    /** */
343                    PLACEHOLDER_TIMEOUT(10);
344                    
345                    private static final Map<Integer,FAILUREREASON> lookup = new HashMap<Integer,FAILUREREASON>();
346                    
347                    static {
348                            for(FAILUREREASON s : EnumSet.allOf(FAILUREREASON.class))
349                                    lookup.put(s.getId(), s);
350                    }
351                    
352                    private final int id;
353                    
354                    private FAILUREREASON(int value) {
355                            this.id = value;
356                    }
357                    
358                    public int getId() { return id; }
359                    
360                    public static FAILUREREASON get(int code) {
361                            return lookup.get(code);
362                    }
363                    
364                    public static FAILUREREASON fromString(String s) {
365                            for (FAILUREREASON p : lookup.values()) {
366                                    if (p.toString() == s) {
367                                            return p;
368                                    }
369                            }
370                            return null;
371                    }
372            }
373            
374            /**
375             *Accepts an incoming file transfer and saves it to specified file on the local file system. If the specified file exists, SkypeKit will silently overwrite it. Your UI should prompting the user for confirmation in this case and provide a means for canceling the file transfer or specifying a different target file. <br>
376             * @param filenameWithPath Where on the local file system to save the file being transferred. Note that you should specify the path as being fully-qualified. Otherwise, SkypeKit will be assume it to be relative to the SkypeKit runtime path, since the method is actually executed in the runtime context. <br>
377             * @return success Set to true if the specified target file was successfully created on the local file system -and- the initial write(s) succeeded. However, the transfer itself can subsequently fail before completion due to its being canceled (either locally or remotely), network failure, local file system space/write issues, and so forth. <br>
378             */
379            public boolean Accept( String filenameWithPath) {
380            
381                    Request request = null;
382                    try {
383                            request = new XCallRequest(6,3);
384                    } catch (IOException e) {
385                            e.printStackTrace();
386                            if (skype.errorListener != null)
387                                    skype.errorListener.OnSkypeKitFatalError();
388                    }
389                    request.addParm('O',0,mObjectId);
390                    request.addParm('f',1,filenameWithPath);
391                    
392                    Response r = skype.XCall((XCallRequest)request);
393                    
394                    if (r.isErrCall())
395                            return false;
396                            
397                    boolean success = false;
398                    success = r.GetAsBoolean(1);
399                    return success;
400            }
401            
402            /**
403             *Temporarily pauses an in-progress incoming or outgoing file transfer. For incoming transfers, only this affects the sender and the invoking recipient only. For outgoing transfers, this affects the sender and all recipients. <br>
404             */
405            public void Pause() {
406            
407                    Request request = null;
408                    try {
409                            request = new XCallRequest(6,4);
410                    } catch (IOException e) {
411                            e.printStackTrace();
412                            if (skype.errorListener != null)
413                                    skype.errorListener.OnSkypeKitFatalError();
414                    }
415                    request.addParm('O',0,mObjectId);
416                    
417                    skype.XCall((XCallRequest)request);
418            }
419            
420            /**
421             *Resumes a previously paused file transfer. <br>
422             */
423            public void Resume() {
424            
425                    Request request = null;
426                    try {
427                            request = new XCallRequest(6,5);
428                    } catch (IOException e) {
429                            e.printStackTrace();
430                            if (skype.errorListener != null)
431                                    skype.errorListener.OnSkypeKitFatalError();
432                    }
433                    request.addParm('O',0,mObjectId);
434                    
435                    skype.XCall((XCallRequest)request);
436            }
437            
438            /**
439             *Cancels an in-progress file transfer. Transfer.STATUS will transition to CANCELLED for incoming file transfers and to CANCELLED_BY_REMOTE for outgoing transfers. <br>
440             */
441            public void Cancel() {
442            
443                    Request request = null;
444                    try {
445                            request = new XCallRequest(6,6);
446                    } catch (IOException e) {
447                            e.printStackTrace();
448                            if (skype.errorListener != null)
449                                    skype.errorListener.OnSkypeKitFatalError();
450                    }
451                    request.addParm('O',0,mObjectId);
452                    
453                    skype.XCall((XCallRequest)request);
454            }
455            
456    
457    }