1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Mike Pinkerton (pinkerton@netscape.com)
24 : * Dainis Jonitis (Dainis_Jonitis@swh-t.lv)
25 : * Mats Palmgren <matpal@gmail.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : /*
42 : Notes to self:
43 :
44 : - at some point, strings will be accessible from JS, so we won't have to wrap
45 : flavors in an nsISupportsCString. Until then, we're kinda stuck with
46 : this crappy API of nsISupportsArrays.
47 :
48 : */
49 :
50 :
51 : #include "nsTransferable.h"
52 : #include "nsString.h"
53 : #include "nsReadableUtils.h"
54 : #include "nsTArray.h"
55 : #include "nsIFormatConverter.h"
56 : #include "nsIComponentManager.h"
57 : #include "nsCOMPtr.h"
58 : #include "nsXPCOM.h"
59 : #include "nsISupportsPrimitives.h"
60 : #include "nsMemory.h"
61 : #include "nsPrimitiveHelpers.h"
62 : #include "nsXPIDLString.h"
63 : #include "nsDirectoryServiceDefs.h"
64 : #include "nsDirectoryService.h"
65 : #include "nsCRT.h"
66 : #include "nsNetUtil.h"
67 : #include "nsIOutputStream.h"
68 : #include "nsIInputStream.h"
69 : #include "nsIFile.h"
70 : #include "nsAutoPtr.h"
71 :
72 0 : NS_IMPL_ISUPPORTS1(nsTransferable, nsITransferable)
73 :
74 0 : PRUint32 GetDataForFlavor (const nsTArray<DataStruct>& aArray,
75 : const char* aDataFlavor)
76 : {
77 0 : for (PRUint32 i = 0 ; i < aArray.Length () ; ++i) {
78 0 : if (aArray[i].GetFlavor().Equals (aDataFlavor))
79 0 : return i;
80 : }
81 :
82 0 : return aArray.NoIndex;
83 : }
84 :
85 : //-------------------------------------------------------------------------
86 0 : DataStruct::~DataStruct()
87 : {
88 0 : if (mCacheFileName) nsCRT::free(mCacheFileName);
89 :
90 : //nsIFileSpec * cacheFile = GetFileSpec(mCacheFileName);
91 : //cacheFile->Remove();
92 0 : }
93 :
94 : //-------------------------------------------------------------------------
95 : void
96 0 : DataStruct::SetData ( nsISupports* aData, PRUint32 aDataLen )
97 : {
98 : // Now, check to see if we consider the data to be "too large"
99 0 : if (aDataLen > kLargeDatasetSize) {
100 : // if so, cache it to disk instead of memory
101 0 : if ( NS_SUCCEEDED(WriteCache(aData, aDataLen)) )
102 0 : return;
103 : else
104 0 : NS_WARNING("Oh no, couldn't write data to the cache file");
105 : }
106 :
107 0 : mData = aData;
108 0 : mDataLen = aDataLen;
109 : }
110 :
111 :
112 : //-------------------------------------------------------------------------
113 : void
114 0 : DataStruct::GetData ( nsISupports** aData, PRUint32 *aDataLen )
115 : {
116 : // check here to see if the data is cached on disk
117 0 : if ( !mData && mCacheFileName ) {
118 : // if so, read it in and pass it back
119 : // ReadCache creates memory and copies the data into it.
120 0 : if ( NS_SUCCEEDED(ReadCache(aData, aDataLen)) )
121 0 : return;
122 : else {
123 : // oh shit, something went horribly wrong here.
124 0 : NS_WARNING("Oh no, couldn't read data in from the cache file");
125 0 : *aData = nsnull;
126 0 : *aDataLen = 0;
127 0 : return;
128 : }
129 : }
130 :
131 0 : *aData = mData;
132 0 : if ( mData )
133 0 : NS_ADDREF(*aData);
134 0 : *aDataLen = mDataLen;
135 : }
136 :
137 :
138 : //-------------------------------------------------------------------------
139 : nsIFile*
140 0 : DataStruct::GetFileSpec(const char * aFileName)
141 : {
142 : nsIFile* cacheFile;
143 0 : NS_GetSpecialDirectory(NS_OS_TEMP_DIR, &cacheFile);
144 :
145 0 : if (cacheFile == nsnull)
146 0 : return nsnull;
147 :
148 : // if the param aFileName contains a name we should use that
149 : // because the file probably already exists
150 : // otherwise create a unique name
151 0 : if (!aFileName) {
152 0 : cacheFile->AppendNative(NS_LITERAL_CSTRING("clipboardcache"));
153 0 : cacheFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
154 : } else {
155 0 : cacheFile->AppendNative(nsDependentCString(aFileName));
156 : }
157 :
158 0 : return cacheFile;
159 : }
160 :
161 :
162 : //-------------------------------------------------------------------------
163 : nsresult
164 0 : DataStruct::WriteCache(nsISupports* aData, PRUint32 aDataLen)
165 : {
166 : // Get a new path and file to the temp directory
167 0 : nsCOMPtr<nsIFile> cacheFile ( getter_AddRefs(GetFileSpec(mCacheFileName)) );
168 0 : if (cacheFile) {
169 : // remember the file name
170 0 : if (!mCacheFileName) {
171 0 : nsXPIDLCString fName;
172 0 : cacheFile->GetNativeLeafName(fName);
173 0 : mCacheFileName = nsCRT::strdup(fName);
174 : }
175 :
176 : // write out the contents of the clipboard
177 : // to the file
178 : //PRUint32 bytes;
179 0 : nsCOMPtr<nsIOutputStream> outStr;
180 :
181 0 : NS_NewLocalFileOutputStream(getter_AddRefs(outStr),
182 0 : cacheFile);
183 :
184 0 : if (!outStr) return NS_ERROR_FAILURE;
185 :
186 0 : void* buff = nsnull;
187 0 : nsPrimitiveHelpers::CreateDataFromPrimitive ( mFlavor.get(), aData, &buff, aDataLen );
188 0 : if ( buff ) {
189 : PRUint32 ignored;
190 0 : outStr->Write(reinterpret_cast<char*>(buff), aDataLen, &ignored);
191 0 : nsMemory::Free(buff);
192 0 : return NS_OK;
193 : }
194 : }
195 0 : return NS_ERROR_FAILURE;
196 : }
197 :
198 :
199 : //-------------------------------------------------------------------------
200 : nsresult
201 0 : DataStruct::ReadCache(nsISupports** aData, PRUint32* aDataLen)
202 : {
203 : // if we don't have a cache filename we are out of luck
204 0 : if (!mCacheFileName)
205 0 : return NS_ERROR_FAILURE;
206 :
207 : // get the path and file name
208 0 : nsCOMPtr<nsIFile> cacheFile ( getter_AddRefs(GetFileSpec(mCacheFileName)) );
209 : bool exists;
210 0 : if ( cacheFile && NS_SUCCEEDED(cacheFile->Exists(&exists)) && exists ) {
211 : // get the size of the file
212 : PRInt64 fileSize;
213 0 : PRInt64 max32(LL_INIT(0, 0xFFFFFFFF));
214 0 : cacheFile->GetFileSize(&fileSize);
215 0 : if (LL_CMP(fileSize, >, max32))
216 0 : return NS_ERROR_OUT_OF_MEMORY;
217 : PRUint32 size;
218 0 : LL_L2UI(size, fileSize);
219 :
220 : // create new memory for the large clipboard data
221 0 : nsAutoArrayPtr<char> data(new char[size]);
222 0 : if ( !data )
223 0 : return NS_ERROR_OUT_OF_MEMORY;
224 :
225 : // now read it all in
226 0 : nsCOMPtr<nsIInputStream> inStr;
227 0 : NS_NewLocalFileInputStream( getter_AddRefs(inStr),
228 0 : cacheFile);
229 :
230 0 : if (!cacheFile) return NS_ERROR_FAILURE;
231 :
232 0 : nsresult rv = inStr->Read(data, fileSize, aDataLen);
233 :
234 : // make sure we got all the data ok
235 0 : if (NS_SUCCEEDED(rv) && *aDataLen == size) {
236 0 : nsPrimitiveHelpers::CreatePrimitiveForData ( mFlavor.get(), data, fileSize, aData );
237 0 : return *aData ? NS_OK : NS_ERROR_FAILURE;
238 : }
239 :
240 : // zero the return params
241 0 : *aData = nsnull;
242 0 : *aDataLen = 0;
243 : }
244 :
245 0 : return NS_ERROR_FAILURE;
246 : }
247 :
248 :
249 : //-------------------------------------------------------------------------
250 : //
251 : // Transferable constructor
252 : //
253 : //-------------------------------------------------------------------------
254 0 : nsTransferable::nsTransferable()
255 : {
256 0 : }
257 :
258 : //-------------------------------------------------------------------------
259 : //
260 : // Transferable destructor
261 : //
262 : //-------------------------------------------------------------------------
263 0 : nsTransferable::~nsTransferable()
264 : {
265 0 : }
266 :
267 :
268 : //
269 : // GetTransferDataFlavors
270 : //
271 : // Returns a copy of the internal list of flavors. This does NOT take into
272 : // account any converter that may be registered. This list consists of
273 : // nsISupportsCString objects so that the flavor list can be accessed from JS.
274 : //
275 : nsresult
276 0 : nsTransferable::GetTransferDataFlavors(nsISupportsArray ** aDataFlavorList)
277 : {
278 0 : nsresult rv = NS_NewISupportsArray ( aDataFlavorList );
279 0 : if (NS_FAILED(rv)) return rv;
280 :
281 0 : for ( PRUint32 i=0; i<mDataArray.Length(); ++i ) {
282 0 : DataStruct& data = mDataArray.ElementAt(i);
283 0 : nsCOMPtr<nsISupportsCString> flavorWrapper = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
284 0 : if ( flavorWrapper ) {
285 0 : flavorWrapper->SetData ( data.GetFlavor() );
286 0 : nsCOMPtr<nsISupports> genericWrapper ( do_QueryInterface(flavorWrapper) );
287 0 : (*aDataFlavorList)->AppendElement( genericWrapper );
288 : }
289 : }
290 :
291 0 : return NS_OK;
292 : }
293 :
294 :
295 : //
296 : // GetTransferData
297 : //
298 : // Returns the data of the requested flavor, obtained from either having the data on hand or
299 : // using a converter to get it. The data is wrapped in a nsISupports primitive so that it is
300 : // accessible from JS.
301 : //
302 : NS_IMETHODIMP
303 0 : nsTransferable::GetTransferData(const char *aFlavor, nsISupports **aData, PRUint32 *aDataLen)
304 : {
305 0 : NS_ENSURE_ARG_POINTER(aFlavor && aData && aDataLen);
306 :
307 0 : nsresult rv = NS_OK;
308 0 : nsCOMPtr<nsISupports> savedData;
309 :
310 : // first look and see if the data is present in one of the intrinsic flavors
311 : PRUint32 i;
312 0 : for (i = 0; i < mDataArray.Length(); ++i ) {
313 0 : DataStruct& data = mDataArray.ElementAt(i);
314 0 : if ( data.GetFlavor().Equals(aFlavor) ) {
315 0 : nsCOMPtr<nsISupports> dataBytes;
316 : PRUint32 len;
317 0 : data.GetData(getter_AddRefs(dataBytes), &len);
318 0 : if (len == kFlavorHasDataProvider && dataBytes) {
319 : // do we have a data provider?
320 0 : nsCOMPtr<nsIFlavorDataProvider> dataProvider = do_QueryInterface(dataBytes);
321 0 : if (dataProvider) {
322 0 : rv = dataProvider->GetFlavorData(this, aFlavor,
323 0 : getter_AddRefs(dataBytes), &len);
324 0 : if (NS_FAILED(rv))
325 : break; // the provider failed. fall into the converter code below.
326 : }
327 : }
328 0 : if (dataBytes && len > 0) { // XXXmats why is zero length not ok?
329 0 : *aDataLen = len;
330 0 : dataBytes.forget(aData);
331 0 : return NS_OK;
332 : }
333 0 : savedData = dataBytes; // return this if format converter fails
334 0 : break;
335 : }
336 : }
337 :
338 0 : bool found = false;
339 :
340 : // if not, try using a format converter to get the requested flavor
341 0 : if ( mFormatConv ) {
342 0 : for (i = 0; i < mDataArray.Length(); ++i) {
343 0 : DataStruct& data = mDataArray.ElementAt(i);
344 0 : bool canConvert = false;
345 0 : mFormatConv->CanConvert(data.GetFlavor().get(), aFlavor, &canConvert);
346 0 : if ( canConvert ) {
347 0 : nsCOMPtr<nsISupports> dataBytes;
348 : PRUint32 len;
349 0 : data.GetData(getter_AddRefs(dataBytes), &len);
350 0 : if (len == kFlavorHasDataProvider && dataBytes) {
351 : // do we have a data provider?
352 0 : nsCOMPtr<nsIFlavorDataProvider> dataProvider = do_QueryInterface(dataBytes);
353 0 : if (dataProvider) {
354 0 : rv = dataProvider->GetFlavorData(this, aFlavor,
355 0 : getter_AddRefs(dataBytes), &len);
356 0 : if (NS_FAILED(rv))
357 : break; // give up
358 : }
359 : }
360 0 : mFormatConv->Convert(data.GetFlavor().get(), dataBytes, len, aFlavor, aData, aDataLen);
361 0 : found = true;
362 : break;
363 : }
364 : }
365 : }
366 :
367 : // for backward compatibility
368 0 : if (!found) {
369 0 : savedData.forget(aData);
370 0 : *aDataLen = 0;
371 : }
372 :
373 0 : return found ? NS_OK : NS_ERROR_FAILURE;
374 : }
375 :
376 :
377 : //
378 : // GetAnyTransferData
379 : //
380 : // Returns the data of the first flavor found. Caller is responsible for deleting the
381 : // flavor string.
382 : //
383 : NS_IMETHODIMP
384 0 : nsTransferable::GetAnyTransferData(char **aFlavor, nsISupports **aData, PRUint32 *aDataLen)
385 : {
386 0 : NS_ENSURE_ARG_POINTER(aFlavor && aData && aDataLen);
387 :
388 0 : for ( PRUint32 i=0; i < mDataArray.Length(); ++i ) {
389 0 : DataStruct& data = mDataArray.ElementAt(i);
390 0 : if (data.IsDataAvailable()) {
391 0 : *aFlavor = ToNewCString(data.GetFlavor());
392 0 : data.GetData(aData, aDataLen);
393 0 : return NS_OK;
394 : }
395 : }
396 :
397 0 : return NS_ERROR_FAILURE;
398 : }
399 :
400 :
401 : //
402 : // SetTransferData
403 : //
404 : //
405 : //
406 : NS_IMETHODIMP
407 0 : nsTransferable::SetTransferData(const char *aFlavor, nsISupports *aData, PRUint32 aDataLen)
408 : {
409 0 : NS_ENSURE_ARG(aFlavor);
410 :
411 : // first check our intrinsic flavors to see if one has been registered.
412 0 : PRUint32 i = 0;
413 0 : for (i = 0; i < mDataArray.Length(); ++i) {
414 0 : DataStruct& data = mDataArray.ElementAt(i);
415 0 : if ( data.GetFlavor().Equals(aFlavor) ) {
416 0 : data.SetData ( aData, aDataLen );
417 0 : return NS_OK;
418 : }
419 : }
420 :
421 : // if not, try using a format converter to find a flavor to put the data in
422 0 : if ( mFormatConv ) {
423 0 : for (i = 0; i < mDataArray.Length(); ++i) {
424 0 : DataStruct& data = mDataArray.ElementAt(i);
425 0 : bool canConvert = false;
426 0 : mFormatConv->CanConvert(aFlavor, data.GetFlavor().get(), &canConvert);
427 :
428 0 : if ( canConvert ) {
429 0 : nsCOMPtr<nsISupports> ConvertedData;
430 : PRUint32 ConvertedLen;
431 0 : mFormatConv->Convert(aFlavor, aData, aDataLen, data.GetFlavor().get(), getter_AddRefs(ConvertedData), &ConvertedLen);
432 0 : data.SetData(ConvertedData, ConvertedLen);
433 0 : return NS_OK;
434 : }
435 : }
436 : }
437 :
438 : // Can't set data neither directly nor through converter. Just add this flavor and try again
439 0 : nsresult result = NS_ERROR_FAILURE;
440 0 : if ( NS_SUCCEEDED(AddDataFlavor(aFlavor)) )
441 0 : result = SetTransferData (aFlavor, aData, aDataLen);
442 :
443 0 : return result;
444 : }
445 :
446 :
447 : //
448 : // AddDataFlavor
449 : //
450 : // Adds a data flavor to our list with no data. Error if it already exists.
451 : //
452 : NS_IMETHODIMP
453 0 : nsTransferable::AddDataFlavor(const char *aDataFlavor)
454 : {
455 0 : if (GetDataForFlavor (mDataArray, aDataFlavor) != mDataArray.NoIndex)
456 0 : return NS_ERROR_FAILURE;
457 :
458 : // Create a new "slot" for the data
459 0 : mDataArray.AppendElement(DataStruct ( aDataFlavor ));
460 :
461 0 : return NS_OK;
462 : }
463 :
464 :
465 : //
466 : // RemoveDataFlavor
467 : //
468 : // Removes a data flavor (and causes the data to be destroyed). Error if
469 : // the requested flavor is not present.
470 : //
471 : NS_IMETHODIMP
472 0 : nsTransferable::RemoveDataFlavor(const char *aDataFlavor)
473 : {
474 : PRUint32 idx;
475 0 : if ((idx = GetDataForFlavor(mDataArray, aDataFlavor)) != mDataArray.NoIndex) {
476 0 : mDataArray.RemoveElementAt (idx);
477 0 : return NS_OK;
478 : }
479 0 : return NS_ERROR_FAILURE;
480 : }
481 :
482 :
483 : /**
484 : *
485 : *
486 : */
487 : NS_IMETHODIMP
488 0 : nsTransferable::IsLargeDataSet(bool *_retval)
489 : {
490 0 : NS_ENSURE_ARG_POINTER(_retval);
491 0 : *_retval = false;
492 0 : return NS_OK;
493 : }
494 :
495 :
496 : /**
497 : *
498 : *
499 : */
500 0 : NS_IMETHODIMP nsTransferable::SetConverter(nsIFormatConverter * aConverter)
501 : {
502 0 : mFormatConv = aConverter;
503 0 : return NS_OK;
504 : }
505 :
506 :
507 : /**
508 : *
509 : *
510 : */
511 0 : NS_IMETHODIMP nsTransferable::GetConverter(nsIFormatConverter * *aConverter)
512 : {
513 0 : NS_ENSURE_ARG_POINTER(aConverter);
514 0 : *aConverter = mFormatConv;
515 0 : NS_IF_ADDREF(*aConverter);
516 0 : return NS_OK;
517 : }
518 :
519 :
520 : //
521 : // FlavorsTransferableCanImport
522 : //
523 : // Computes a list of flavors that the transferable can accept into it, either through
524 : // intrinsic knowledge or input data converters.
525 : //
526 : NS_IMETHODIMP
527 0 : nsTransferable::FlavorsTransferableCanImport(nsISupportsArray **_retval)
528 : {
529 0 : NS_ENSURE_ARG_POINTER(_retval);
530 :
531 : // Get the flavor list, and on to the end of it, append the list of flavors we
532 : // can also get to through a converter. This is so that we can just walk the list
533 : // in one go, looking for the desired flavor.
534 0 : GetTransferDataFlavors(_retval); // addrefs
535 0 : nsCOMPtr<nsIFormatConverter> converter;
536 0 : GetConverter(getter_AddRefs(converter));
537 0 : if ( converter ) {
538 0 : nsCOMPtr<nsISupportsArray> convertedList;
539 0 : converter->GetInputDataFlavors(getter_AddRefs(convertedList));
540 :
541 0 : if ( convertedList ) {
542 : PRUint32 importListLen;
543 0 : convertedList->Count(&importListLen);
544 :
545 0 : for ( PRUint32 i=0; i < importListLen; ++i ) {
546 0 : nsCOMPtr<nsISupports> genericFlavor;
547 0 : convertedList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
548 :
549 0 : nsCOMPtr<nsISupportsCString> flavorWrapper ( do_QueryInterface (genericFlavor) );
550 0 : nsCAutoString flavorStr;
551 0 : flavorWrapper->GetData( flavorStr );
552 :
553 0 : if (GetDataForFlavor (mDataArray, flavorStr.get())
554 : == mDataArray.NoIndex) // Don't append if already in intrinsic list
555 0 : (*_retval)->AppendElement (genericFlavor);
556 : } // foreach flavor that can be converted to
557 : }
558 : } // if a converter exists
559 :
560 0 : return NS_OK;
561 : } // FlavorsTransferableCanImport
562 :
563 :
564 : //
565 : // FlavorsTransferableCanExport
566 : //
567 : // Computes a list of flavors that the transferable can export, either through
568 : // intrinsic knowledge or output data converters.
569 : //
570 : NS_IMETHODIMP
571 0 : nsTransferable::FlavorsTransferableCanExport(nsISupportsArray **_retval)
572 : {
573 0 : NS_ENSURE_ARG_POINTER(_retval);
574 :
575 : // Get the flavor list, and on to the end of it, append the list of flavors we
576 : // can also get to through a converter. This is so that we can just walk the list
577 : // in one go, looking for the desired flavor.
578 0 : GetTransferDataFlavors(_retval); // addrefs
579 0 : nsCOMPtr<nsIFormatConverter> converter;
580 0 : GetConverter(getter_AddRefs(converter));
581 0 : if ( converter ) {
582 0 : nsCOMPtr<nsISupportsArray> convertedList;
583 0 : converter->GetOutputDataFlavors(getter_AddRefs(convertedList));
584 :
585 0 : if ( convertedList ) {
586 : PRUint32 importListLen;
587 0 : convertedList->Count(&importListLen);
588 :
589 0 : for ( PRUint32 i=0; i < importListLen; ++i ) {
590 0 : nsCOMPtr<nsISupports> genericFlavor;
591 0 : convertedList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
592 :
593 0 : nsCOMPtr<nsISupportsCString> flavorWrapper ( do_QueryInterface (genericFlavor) );
594 0 : nsCAutoString flavorStr;
595 0 : flavorWrapper->GetData( flavorStr );
596 :
597 0 : if (GetDataForFlavor (mDataArray, flavorStr.get())
598 : == mDataArray.NoIndex) // Don't append if already in intrinsic list
599 0 : (*_retval)->AppendElement (genericFlavor);
600 : } // foreach flavor that can be converted to
601 : }
602 : } // if a converter exists
603 :
604 0 : return NS_OK;
605 : } // FlavorsTransferableCanExport
|