    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    }