1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 #if JUCE_CONTENT_SHARING
30 //==============================================================================
31 class ContentSharer::PrepareImagesThread    : private Thread
32 {
33 public:
PrepareImagesThread(ContentSharer & cs,const Array<Image> & imagesToUse,ImageFileFormat * imageFileFormatToUse)34     PrepareImagesThread (ContentSharer& cs, const Array<Image>& imagesToUse,
35                          ImageFileFormat* imageFileFormatToUse)
36         : Thread ("ContentSharer::PrepareImagesThread"),
37           owner (cs),
38           images (imagesToUse),
39           imageFileFormat (imageFileFormatToUse == nullptr ? new PNGImageFormat()
40                                                            : imageFileFormatToUse),
41           extension (imageFileFormat->getFormatName().toLowerCase())
42     {
43         startThread();
44     }
45 
~PrepareImagesThread()46     ~PrepareImagesThread() override
47     {
48         signalThreadShouldExit();
49         waitForThreadToExit (10000);
50     }
51 
52 private:
run()53     void run() override
54     {
55         for (const auto& image : images)
56         {
57             if (threadShouldExit())
58                 return;
59 
60             File tempFile = File::createTempFile (extension);
61 
62             if (! tempFile.create().wasOk())
63                 break;
64 
65             std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
66 
67             if (outputStream == nullptr)
68                 break;
69 
70             if (imageFileFormat->writeImageToStream (image, *outputStream))
71                 owner.temporaryFiles.add (tempFile);
72         }
73 
74         finish();
75     }
76 
finish()77     void finish()
78     {
79         MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
80     }
81 
82     ContentSharer& owner;
83     const Array<Image> images;
84     std::unique_ptr<ImageFileFormat> imageFileFormat;
85     String extension;
86 };
87 
88 //==============================================================================
89 class ContentSharer::PrepareDataThread    : private Thread
90 {
91 public:
PrepareDataThread(ContentSharer & cs,const MemoryBlock & mb)92     PrepareDataThread (ContentSharer& cs, const MemoryBlock& mb)
93         : Thread ("ContentSharer::PrepareDataThread"),
94           owner (cs),
95           data (mb)
96     {
97         startThread();
98     }
99 
~PrepareDataThread()100     ~PrepareDataThread() override
101     {
102         signalThreadShouldExit();
103         waitForThreadToExit (10000);
104     }
105 
106 private:
run()107     void run() override
108     {
109         File tempFile = File::createTempFile ("data");
110 
111         if (tempFile.create().wasOk())
112         {
113             if (auto outputStream = std::unique_ptr<FileOutputStream> (tempFile.createOutputStream()))
114             {
115                 size_t pos = 0;
116                 size_t totalSize = data.getSize();
117 
118                 while (pos < totalSize)
119                 {
120                     if (threadShouldExit())
121                         return;
122 
123                     size_t numToWrite = std::min ((size_t) 8192, totalSize - pos);
124 
125                     outputStream->write (data.begin() + pos, numToWrite);
126 
127                     pos += numToWrite;
128                 }
129 
130                 owner.temporaryFiles.add (tempFile);
131             }
132         }
133 
134         finish();
135     }
136 
finish()137     void finish()
138     {
139         MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
140     }
141 
142     ContentSharer& owner;
143     const MemoryBlock data;
144 };
145 #endif
146 
147 //==============================================================================
JUCE_IMPLEMENT_SINGLETON(ContentSharer)148 JUCE_IMPLEMENT_SINGLETON (ContentSharer)
149 
150 ContentSharer::ContentSharer() {}
~ContentSharer()151 ContentSharer::~ContentSharer() { clearSingletonInstance(); }
152 
shareFiles(const Array<URL> & files,std::function<void (bool,const String &)> callbackToUse)153 void ContentSharer::shareFiles (const Array<URL>& files,
154                                 std::function<void (bool, const String&)> callbackToUse)
155 {
156   #if JUCE_CONTENT_SHARING
157     startNewShare (callbackToUse);
158     pimpl->shareFiles (files);
159   #else
160     ignoreUnused (files);
161 
162     // Content sharing is not available on this platform!
163     jassertfalse;
164 
165     if (callbackToUse)
166         callbackToUse (false, "Content sharing is not available on this platform!");
167   #endif
168 }
169 
170 #if JUCE_CONTENT_SHARING
startNewShare(std::function<void (bool,const String &)> callbackToUse)171 void ContentSharer::startNewShare (std::function<void (bool, const String&)> callbackToUse)
172 {
173     // You should not start another sharing operation before the previous one is finished.
174     // Forcibly stopping a previous sharing operation is rarely a good idea!
175     jassert (pimpl == nullptr);
176     pimpl.reset();
177 
178     prepareDataThread = nullptr;
179     prepareImagesThread = nullptr;
180 
181     deleteTemporaryFiles();
182 
183     // You need to pass a valid callback.
184     jassert (callbackToUse);
185     callback = std::move (callbackToUse);
186 
187     pimpl.reset (createPimpl());
188 }
189 #endif
190 
shareText(const String & text,std::function<void (bool,const String &)> callbackToUse)191 void ContentSharer::shareText (const String& text,
192                                std::function<void (bool, const String&)> callbackToUse)
193 {
194   #if JUCE_CONTENT_SHARING
195     startNewShare (callbackToUse);
196     pimpl->shareText (text);
197   #else
198     ignoreUnused (text);
199 
200     // Content sharing is not available on this platform!
201     jassertfalse;
202 
203     if (callbackToUse)
204         callbackToUse (false, "Content sharing is not available on this platform!");
205   #endif
206 }
207 
shareImages(const Array<Image> & images,std::function<void (bool,const String &)> callbackToUse,ImageFileFormat * imageFileFormatToUse)208 void ContentSharer::shareImages (const Array<Image>& images,
209                                  std::function<void (bool, const String&)> callbackToUse,
210                                  ImageFileFormat* imageFileFormatToUse)
211 {
212   #if JUCE_CONTENT_SHARING
213     startNewShare (callbackToUse);
214     prepareImagesThread.reset (new PrepareImagesThread (*this, images, imageFileFormatToUse));
215   #else
216     ignoreUnused (images, imageFileFormatToUse);
217 
218     // Content sharing is not available on this platform!
219     jassertfalse;
220 
221     if (callbackToUse)
222         callbackToUse (false, "Content sharing is not available on this platform!");
223   #endif
224 }
225 
226 #if JUCE_CONTENT_SHARING
filesToSharePrepared()227 void ContentSharer::filesToSharePrepared()
228 {
229     Array<URL> urls;
230 
231     for (const auto& tempFile : temporaryFiles)
232         urls.add (URL (tempFile));
233 
234     prepareImagesThread = nullptr;
235     prepareDataThread = nullptr;
236 
237     pimpl->shareFiles (urls);
238 }
239 #endif
240 
shareData(const MemoryBlock & mb,std::function<void (bool,const String &)> callbackToUse)241 void ContentSharer::shareData (const MemoryBlock& mb,
242                                std::function<void (bool, const String&)> callbackToUse)
243 {
244   #if JUCE_CONTENT_SHARING
245     startNewShare (callbackToUse);
246     prepareDataThread.reset (new PrepareDataThread (*this, mb));
247   #else
248     ignoreUnused (mb);
249 
250     if (callbackToUse)
251         callbackToUse (false, "Content sharing not available on this platform!");
252   #endif
253 }
254 
sharingFinished(bool succeeded,const String & errorDescription)255 void ContentSharer::sharingFinished (bool succeeded, const String& errorDescription)
256 {
257     deleteTemporaryFiles();
258 
259     std::function<void (bool, String)> cb;
260     std::swap (cb, callback);
261 
262     String error (errorDescription);
263 
264   #if JUCE_CONTENT_SHARING
265     pimpl.reset();
266   #endif
267 
268     if (cb)
269         cb (succeeded, error);
270 }
271 
deleteTemporaryFiles()272 void ContentSharer::deleteTemporaryFiles()
273 {
274     for (auto& f : temporaryFiles)
275         f.deleteFile();
276 
277     temporaryFiles.clear();
278 }
279 
280 } // namespace juce
281