Merge lp:~jamesh/storage-provider-webdav/landing-20161118 into lp:storage-provider-webdav

Proposed by James Henstridge
Status: Merged
Approved by: Michi Henning
Approved revision: 21
Merged at revision: 11
Proposed branch: lp:~jamesh/storage-provider-webdav/landing-20161118
Merge into: lp:storage-provider-webdav
Diff against target: 5576 lines (+4454/-257)
56 files modified
CMakeLists.txt (+3/-2)
COPYING.GPL (+674/-0)
COPYING.LGPL (+165/-0)
data/CMakeLists.txt (+17/-0)
data/com.canonical.StorageFramework.Provider.OwnCloud.service.in (+3/-0)
data/storage-provider-owncloud.application (+12/-0)
data/storage-provider-owncloud.desktop (+5/-0)
data/storage-provider-owncloud.service (+8/-0)
debian/control (+8/-7)
debian/copyright (+36/-0)
src/CMakeLists.txt (+17/-1)
src/CopyMoveHandler.cpp (+85/-0)
src/CopyMoveHandler.h (+57/-0)
src/CreateFolderHandler.cpp (+75/-0)
src/CreateFolderHandler.h (+53/-0)
src/DavDownloadJob.cpp (+215/-0)
src/DavDownloadJob.h (+72/-0)
src/DavProvider.cpp (+183/-13)
src/DavProvider.h (+35/-0)
src/DavUploadJob.cpp (+140/-0)
src/DavUploadJob.h (+65/-0)
src/DeleteHandler.cpp (+62/-0)
src/DeleteHandler.h (+49/-0)
src/ListHandler.cpp (+59/-0)
src/ListHandler.h (+43/-0)
src/LookupHandler.cpp (+54/-0)
src/LookupHandler.h (+42/-0)
src/MetadataHandler.cpp (+61/-0)
src/MetadataHandler.h (+42/-0)
src/MultiStatusParser.cpp (+28/-4)
src/MultiStatusParser.h (+18/-0)
src/OwncloudProvider.cpp (+25/-3)
src/OwncloudProvider.h (+18/-0)
src/PropFindHandler.cpp (+127/-131)
src/PropFindHandler.h (+43/-16)
src/RetrieveMetadataHandler.cpp (+52/-0)
src/RetrieveMetadataHandler.h (+45/-0)
src/RootsHandler.cpp (+61/-0)
src/RootsHandler.h (+41/-0)
src/http_error.cpp (+181/-0)
src/http_error.h (+30/-0)
src/item_id.cpp (+59/-0)
src/item_id.h (+23/-0)
src/main.cpp (+19/-1)
tests/CMakeLists.txt (+1/-6)
tests/davprovider/davprovider_test.cpp (+843/-32)
tests/http_error/CMakeLists.txt (+8/-0)
tests/http_error/http_error_test.cpp (+278/-0)
tests/item_id/item_id_test.cpp (+46/-0)
tests/multistatus/multistatus_test.cpp (+24/-6)
tests/utils/DavEnvironment.cpp (+18/-0)
tests/utils/DavEnvironment.h (+18/-0)
tests/utils/ProviderEnvironment.cpp (+27/-7)
tests/utils/ProviderEnvironment.h (+25/-5)
tests/utils/fake-online-accounts-daemon.py (+16/-22)
tests/utils/sabredav-server.php (+40/-1)
To merge this branch: bzr merge lp:~jamesh/storage-provider-webdav/landing-20161118
Reviewer Review Type Date Requested Status
Michi Henning (community) Approve
Review via email: mp+311240@code.launchpad.net

Commit message

Description of the change

To post a comment you must log in.
21. By James Henstridge

Remove the item ID check in RetrievMetadataHandler::finish(), since the WebDAV server may encode URIs differently to Qt.

Approved by unity-api-1-bot, Michi Henning.

Revision history for this message
Michi Henning (michihenning) wrote :

Yep!

review: Approve
22. By James Henstridge

Update the test suite to use the v2 client API.

Approved by Michi Henning, unity-api-1-bot.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-09-27 07:08:19 +0000
3+++ CMakeLists.txt 2016-11-25 05:01:23 +0000
4@@ -16,12 +16,13 @@
5 find_package(Qt5Network REQUIRED)
6 find_package(Qt5Test REQUIRED)
7 find_package(Qt5Xml REQUIRED)
8-pkg_check_modules(SF_PROVIDER REQUIRED storage-framework-provider-1)
9-pkg_check_modules(SF_CLIENT REQUIRED storage-framework-qt-client-1)
10+pkg_check_modules(SF_PROVIDER REQUIRED storage-framework-provider-1>=0.2)
11+pkg_check_modules(SF_CLIENT REQUIRED storage-framework-qt-client-2>=0.2)
12
13 add_definitions(-DQT_NO_KEYWORDS)
14
15 add_subdirectory(src)
16+add_subdirectory(data)
17 add_subdirectory(tests)
18
19 enable_coverage_report(
20
21=== added file 'COPYING.GPL'
22--- COPYING.GPL 1970-01-01 00:00:00 +0000
23+++ COPYING.GPL 2016-11-25 05:01:23 +0000
24@@ -0,0 +1,674 @@
25+ GNU GENERAL PUBLIC LICENSE
26+ Version 3, 29 June 2007
27+
28+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
29+ Everyone is permitted to copy and distribute verbatim copies
30+ of this license document, but changing it is not allowed.
31+
32+ Preamble
33+
34+ The GNU General Public License is a free, copyleft license for
35+software and other kinds of works.
36+
37+ The licenses for most software and other practical works are designed
38+to take away your freedom to share and change the works. By contrast,
39+the GNU General Public License is intended to guarantee your freedom to
40+share and change all versions of a program--to make sure it remains free
41+software for all its users. We, the Free Software Foundation, use the
42+GNU General Public License for most of our software; it applies also to
43+any other work released this way by its authors. You can apply it to
44+your programs, too.
45+
46+ When we speak of free software, we are referring to freedom, not
47+price. Our General Public Licenses are designed to make sure that you
48+have the freedom to distribute copies of free software (and charge for
49+them if you wish), that you receive source code or can get it if you
50+want it, that you can change the software or use pieces of it in new
51+free programs, and that you know you can do these things.
52+
53+ To protect your rights, we need to prevent others from denying you
54+these rights or asking you to surrender the rights. Therefore, you have
55+certain responsibilities if you distribute copies of the software, or if
56+you modify it: responsibilities to respect the freedom of others.
57+
58+ For example, if you distribute copies of such a program, whether
59+gratis or for a fee, you must pass on to the recipients the same
60+freedoms that you received. You must make sure that they, too, receive
61+or can get the source code. And you must show them these terms so they
62+know their rights.
63+
64+ Developers that use the GNU GPL protect your rights with two steps:
65+(1) assert copyright on the software, and (2) offer you this License
66+giving you legal permission to copy, distribute and/or modify it.
67+
68+ For the developers' and authors' protection, the GPL clearly explains
69+that there is no warranty for this free software. For both users' and
70+authors' sake, the GPL requires that modified versions be marked as
71+changed, so that their problems will not be attributed erroneously to
72+authors of previous versions.
73+
74+ Some devices are designed to deny users access to install or run
75+modified versions of the software inside them, although the manufacturer
76+can do so. This is fundamentally incompatible with the aim of
77+protecting users' freedom to change the software. The systematic
78+pattern of such abuse occurs in the area of products for individuals to
79+use, which is precisely where it is most unacceptable. Therefore, we
80+have designed this version of the GPL to prohibit the practice for those
81+products. If such problems arise substantially in other domains, we
82+stand ready to extend this provision to those domains in future versions
83+of the GPL, as needed to protect the freedom of users.
84+
85+ Finally, every program is threatened constantly by software patents.
86+States should not allow patents to restrict development and use of
87+software on general-purpose computers, but in those that do, we wish to
88+avoid the special danger that patents applied to a free program could
89+make it effectively proprietary. To prevent this, the GPL assures that
90+patents cannot be used to render the program non-free.
91+
92+ The precise terms and conditions for copying, distribution and
93+modification follow.
94+
95+ TERMS AND CONDITIONS
96+
97+ 0. Definitions.
98+
99+ "This License" refers to version 3 of the GNU General Public License.
100+
101+ "Copyright" also means copyright-like laws that apply to other kinds of
102+works, such as semiconductor masks.
103+
104+ "The Program" refers to any copyrightable work licensed under this
105+License. Each licensee is addressed as "you". "Licensees" and
106+"recipients" may be individuals or organizations.
107+
108+ To "modify" a work means to copy from or adapt all or part of the work
109+in a fashion requiring copyright permission, other than the making of an
110+exact copy. The resulting work is called a "modified version" of the
111+earlier work or a work "based on" the earlier work.
112+
113+ A "covered work" means either the unmodified Program or a work based
114+on the Program.
115+
116+ To "propagate" a work means to do anything with it that, without
117+permission, would make you directly or secondarily liable for
118+infringement under applicable copyright law, except executing it on a
119+computer or modifying a private copy. Propagation includes copying,
120+distribution (with or without modification), making available to the
121+public, and in some countries other activities as well.
122+
123+ To "convey" a work means any kind of propagation that enables other
124+parties to make or receive copies. Mere interaction with a user through
125+a computer network, with no transfer of a copy, is not conveying.
126+
127+ An interactive user interface displays "Appropriate Legal Notices"
128+to the extent that it includes a convenient and prominently visible
129+feature that (1) displays an appropriate copyright notice, and (2)
130+tells the user that there is no warranty for the work (except to the
131+extent that warranties are provided), that licensees may convey the
132+work under this License, and how to view a copy of this License. If
133+the interface presents a list of user commands or options, such as a
134+menu, a prominent item in the list meets this criterion.
135+
136+ 1. Source Code.
137+
138+ The "source code" for a work means the preferred form of the work
139+for making modifications to it. "Object code" means any non-source
140+form of a work.
141+
142+ A "Standard Interface" means an interface that either is an official
143+standard defined by a recognized standards body, or, in the case of
144+interfaces specified for a particular programming language, one that
145+is widely used among developers working in that language.
146+
147+ The "System Libraries" of an executable work include anything, other
148+than the work as a whole, that (a) is included in the normal form of
149+packaging a Major Component, but which is not part of that Major
150+Component, and (b) serves only to enable use of the work with that
151+Major Component, or to implement a Standard Interface for which an
152+implementation is available to the public in source code form. A
153+"Major Component", in this context, means a major essential component
154+(kernel, window system, and so on) of the specific operating system
155+(if any) on which the executable work runs, or a compiler used to
156+produce the work, or an object code interpreter used to run it.
157+
158+ The "Corresponding Source" for a work in object code form means all
159+the source code needed to generate, install, and (for an executable
160+work) run the object code and to modify the work, including scripts to
161+control those activities. However, it does not include the work's
162+System Libraries, or general-purpose tools or generally available free
163+programs which are used unmodified in performing those activities but
164+which are not part of the work. For example, Corresponding Source
165+includes interface definition files associated with source files for
166+the work, and the source code for shared libraries and dynamically
167+linked subprograms that the work is specifically designed to require,
168+such as by intimate data communication or control flow between those
169+subprograms and other parts of the work.
170+
171+ The Corresponding Source need not include anything that users
172+can regenerate automatically from other parts of the Corresponding
173+Source.
174+
175+ The Corresponding Source for a work in source code form is that
176+same work.
177+
178+ 2. Basic Permissions.
179+
180+ All rights granted under this License are granted for the term of
181+copyright on the Program, and are irrevocable provided the stated
182+conditions are met. This License explicitly affirms your unlimited
183+permission to run the unmodified Program. The output from running a
184+covered work is covered by this License only if the output, given its
185+content, constitutes a covered work. This License acknowledges your
186+rights of fair use or other equivalent, as provided by copyright law.
187+
188+ You may make, run and propagate covered works that you do not
189+convey, without conditions so long as your license otherwise remains
190+in force. You may convey covered works to others for the sole purpose
191+of having them make modifications exclusively for you, or provide you
192+with facilities for running those works, provided that you comply with
193+the terms of this License in conveying all material for which you do
194+not control copyright. Those thus making or running the covered works
195+for you must do so exclusively on your behalf, under your direction
196+and control, on terms that prohibit them from making any copies of
197+your copyrighted material outside their relationship with you.
198+
199+ Conveying under any other circumstances is permitted solely under
200+the conditions stated below. Sublicensing is not allowed; section 10
201+makes it unnecessary.
202+
203+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
204+
205+ No covered work shall be deemed part of an effective technological
206+measure under any applicable law fulfilling obligations under article
207+11 of the WIPO copyright treaty adopted on 20 December 1996, or
208+similar laws prohibiting or restricting circumvention of such
209+measures.
210+
211+ When you convey a covered work, you waive any legal power to forbid
212+circumvention of technological measures to the extent such circumvention
213+is effected by exercising rights under this License with respect to
214+the covered work, and you disclaim any intention to limit operation or
215+modification of the work as a means of enforcing, against the work's
216+users, your or third parties' legal rights to forbid circumvention of
217+technological measures.
218+
219+ 4. Conveying Verbatim Copies.
220+
221+ You may convey verbatim copies of the Program's source code as you
222+receive it, in any medium, provided that you conspicuously and
223+appropriately publish on each copy an appropriate copyright notice;
224+keep intact all notices stating that this License and any
225+non-permissive terms added in accord with section 7 apply to the code;
226+keep intact all notices of the absence of any warranty; and give all
227+recipients a copy of this License along with the Program.
228+
229+ You may charge any price or no price for each copy that you convey,
230+and you may offer support or warranty protection for a fee.
231+
232+ 5. Conveying Modified Source Versions.
233+
234+ You may convey a work based on the Program, or the modifications to
235+produce it from the Program, in the form of source code under the
236+terms of section 4, provided that you also meet all of these conditions:
237+
238+ a) The work must carry prominent notices stating that you modified
239+ it, and giving a relevant date.
240+
241+ b) The work must carry prominent notices stating that it is
242+ released under this License and any conditions added under section
243+ 7. This requirement modifies the requirement in section 4 to
244+ "keep intact all notices".
245+
246+ c) You must license the entire work, as a whole, under this
247+ License to anyone who comes into possession of a copy. This
248+ License will therefore apply, along with any applicable section 7
249+ additional terms, to the whole of the work, and all its parts,
250+ regardless of how they are packaged. This License gives no
251+ permission to license the work in any other way, but it does not
252+ invalidate such permission if you have separately received it.
253+
254+ d) If the work has interactive user interfaces, each must display
255+ Appropriate Legal Notices; however, if the Program has interactive
256+ interfaces that do not display Appropriate Legal Notices, your
257+ work need not make them do so.
258+
259+ A compilation of a covered work with other separate and independent
260+works, which are not by their nature extensions of the covered work,
261+and which are not combined with it such as to form a larger program,
262+in or on a volume of a storage or distribution medium, is called an
263+"aggregate" if the compilation and its resulting copyright are not
264+used to limit the access or legal rights of the compilation's users
265+beyond what the individual works permit. Inclusion of a covered work
266+in an aggregate does not cause this License to apply to the other
267+parts of the aggregate.
268+
269+ 6. Conveying Non-Source Forms.
270+
271+ You may convey a covered work in object code form under the terms
272+of sections 4 and 5, provided that you also convey the
273+machine-readable Corresponding Source under the terms of this License,
274+in one of these ways:
275+
276+ a) Convey the object code in, or embodied in, a physical product
277+ (including a physical distribution medium), accompanied by the
278+ Corresponding Source fixed on a durable physical medium
279+ customarily used for software interchange.
280+
281+ b) Convey the object code in, or embodied in, a physical product
282+ (including a physical distribution medium), accompanied by a
283+ written offer, valid for at least three years and valid for as
284+ long as you offer spare parts or customer support for that product
285+ model, to give anyone who possesses the object code either (1) a
286+ copy of the Corresponding Source for all the software in the
287+ product that is covered by this License, on a durable physical
288+ medium customarily used for software interchange, for a price no
289+ more than your reasonable cost of physically performing this
290+ conveying of source, or (2) access to copy the
291+ Corresponding Source from a network server at no charge.
292+
293+ c) Convey individual copies of the object code with a copy of the
294+ written offer to provide the Corresponding Source. This
295+ alternative is allowed only occasionally and noncommercially, and
296+ only if you received the object code with such an offer, in accord
297+ with subsection 6b.
298+
299+ d) Convey the object code by offering access from a designated
300+ place (gratis or for a charge), and offer equivalent access to the
301+ Corresponding Source in the same way through the same place at no
302+ further charge. You need not require recipients to copy the
303+ Corresponding Source along with the object code. If the place to
304+ copy the object code is a network server, the Corresponding Source
305+ may be on a different server (operated by you or a third party)
306+ that supports equivalent copying facilities, provided you maintain
307+ clear directions next to the object code saying where to find the
308+ Corresponding Source. Regardless of what server hosts the
309+ Corresponding Source, you remain obligated to ensure that it is
310+ available for as long as needed to satisfy these requirements.
311+
312+ e) Convey the object code using peer-to-peer transmission, provided
313+ you inform other peers where the object code and Corresponding
314+ Source of the work are being offered to the general public at no
315+ charge under subsection 6d.
316+
317+ A separable portion of the object code, whose source code is excluded
318+from the Corresponding Source as a System Library, need not be
319+included in conveying the object code work.
320+
321+ A "User Product" is either (1) a "consumer product", which means any
322+tangible personal property which is normally used for personal, family,
323+or household purposes, or (2) anything designed or sold for incorporation
324+into a dwelling. In determining whether a product is a consumer product,
325+doubtful cases shall be resolved in favor of coverage. For a particular
326+product received by a particular user, "normally used" refers to a
327+typical or common use of that class of product, regardless of the status
328+of the particular user or of the way in which the particular user
329+actually uses, or expects or is expected to use, the product. A product
330+is a consumer product regardless of whether the product has substantial
331+commercial, industrial or non-consumer uses, unless such uses represent
332+the only significant mode of use of the product.
333+
334+ "Installation Information" for a User Product means any methods,
335+procedures, authorization keys, or other information required to install
336+and execute modified versions of a covered work in that User Product from
337+a modified version of its Corresponding Source. The information must
338+suffice to ensure that the continued functioning of the modified object
339+code is in no case prevented or interfered with solely because
340+modification has been made.
341+
342+ If you convey an object code work under this section in, or with, or
343+specifically for use in, a User Product, and the conveying occurs as
344+part of a transaction in which the right of possession and use of the
345+User Product is transferred to the recipient in perpetuity or for a
346+fixed term (regardless of how the transaction is characterized), the
347+Corresponding Source conveyed under this section must be accompanied
348+by the Installation Information. But this requirement does not apply
349+if neither you nor any third party retains the ability to install
350+modified object code on the User Product (for example, the work has
351+been installed in ROM).
352+
353+ The requirement to provide Installation Information does not include a
354+requirement to continue to provide support service, warranty, or updates
355+for a work that has been modified or installed by the recipient, or for
356+the User Product in which it has been modified or installed. Access to a
357+network may be denied when the modification itself materially and
358+adversely affects the operation of the network or violates the rules and
359+protocols for communication across the network.
360+
361+ Corresponding Source conveyed, and Installation Information provided,
362+in accord with this section must be in a format that is publicly
363+documented (and with an implementation available to the public in
364+source code form), and must require no special password or key for
365+unpacking, reading or copying.
366+
367+ 7. Additional Terms.
368+
369+ "Additional permissions" are terms that supplement the terms of this
370+License by making exceptions from one or more of its conditions.
371+Additional permissions that are applicable to the entire Program shall
372+be treated as though they were included in this License, to the extent
373+that they are valid under applicable law. If additional permissions
374+apply only to part of the Program, that part may be used separately
375+under those permissions, but the entire Program remains governed by
376+this License without regard to the additional permissions.
377+
378+ When you convey a copy of a covered work, you may at your option
379+remove any additional permissions from that copy, or from any part of
380+it. (Additional permissions may be written to require their own
381+removal in certain cases when you modify the work.) You may place
382+additional permissions on material, added by you to a covered work,
383+for which you have or can give appropriate copyright permission.
384+
385+ Notwithstanding any other provision of this License, for material you
386+add to a covered work, you may (if authorized by the copyright holders of
387+that material) supplement the terms of this License with terms:
388+
389+ a) Disclaiming warranty or limiting liability differently from the
390+ terms of sections 15 and 16 of this License; or
391+
392+ b) Requiring preservation of specified reasonable legal notices or
393+ author attributions in that material or in the Appropriate Legal
394+ Notices displayed by works containing it; or
395+
396+ c) Prohibiting misrepresentation of the origin of that material, or
397+ requiring that modified versions of such material be marked in
398+ reasonable ways as different from the original version; or
399+
400+ d) Limiting the use for publicity purposes of names of licensors or
401+ authors of the material; or
402+
403+ e) Declining to grant rights under trademark law for use of some
404+ trade names, trademarks, or service marks; or
405+
406+ f) Requiring indemnification of licensors and authors of that
407+ material by anyone who conveys the material (or modified versions of
408+ it) with contractual assumptions of liability to the recipient, for
409+ any liability that these contractual assumptions directly impose on
410+ those licensors and authors.
411+
412+ All other non-permissive additional terms are considered "further
413+restrictions" within the meaning of section 10. If the Program as you
414+received it, or any part of it, contains a notice stating that it is
415+governed by this License along with a term that is a further
416+restriction, you may remove that term. If a license document contains
417+a further restriction but permits relicensing or conveying under this
418+License, you may add to a covered work material governed by the terms
419+of that license document, provided that the further restriction does
420+not survive such relicensing or conveying.
421+
422+ If you add terms to a covered work in accord with this section, you
423+must place, in the relevant source files, a statement of the
424+additional terms that apply to those files, or a notice indicating
425+where to find the applicable terms.
426+
427+ Additional terms, permissive or non-permissive, may be stated in the
428+form of a separately written license, or stated as exceptions;
429+the above requirements apply either way.
430+
431+ 8. Termination.
432+
433+ You may not propagate or modify a covered work except as expressly
434+provided under this License. Any attempt otherwise to propagate or
435+modify it is void, and will automatically terminate your rights under
436+this License (including any patent licenses granted under the third
437+paragraph of section 11).
438+
439+ However, if you cease all violation of this License, then your
440+license from a particular copyright holder is reinstated (a)
441+provisionally, unless and until the copyright holder explicitly and
442+finally terminates your license, and (b) permanently, if the copyright
443+holder fails to notify you of the violation by some reasonable means
444+prior to 60 days after the cessation.
445+
446+ Moreover, your license from a particular copyright holder is
447+reinstated permanently if the copyright holder notifies you of the
448+violation by some reasonable means, this is the first time you have
449+received notice of violation of this License (for any work) from that
450+copyright holder, and you cure the violation prior to 30 days after
451+your receipt of the notice.
452+
453+ Termination of your rights under this section does not terminate the
454+licenses of parties who have received copies or rights from you under
455+this License. If your rights have been terminated and not permanently
456+reinstated, you do not qualify to receive new licenses for the same
457+material under section 10.
458+
459+ 9. Acceptance Not Required for Having Copies.
460+
461+ You are not required to accept this License in order to receive or
462+run a copy of the Program. Ancillary propagation of a covered work
463+occurring solely as a consequence of using peer-to-peer transmission
464+to receive a copy likewise does not require acceptance. However,
465+nothing other than this License grants you permission to propagate or
466+modify any covered work. These actions infringe copyright if you do
467+not accept this License. Therefore, by modifying or propagating a
468+covered work, you indicate your acceptance of this License to do so.
469+
470+ 10. Automatic Licensing of Downstream Recipients.
471+
472+ Each time you convey a covered work, the recipient automatically
473+receives a license from the original licensors, to run, modify and
474+propagate that work, subject to this License. You are not responsible
475+for enforcing compliance by third parties with this License.
476+
477+ An "entity transaction" is a transaction transferring control of an
478+organization, or substantially all assets of one, or subdividing an
479+organization, or merging organizations. If propagation of a covered
480+work results from an entity transaction, each party to that
481+transaction who receives a copy of the work also receives whatever
482+licenses to the work the party's predecessor in interest had or could
483+give under the previous paragraph, plus a right to possession of the
484+Corresponding Source of the work from the predecessor in interest, if
485+the predecessor has it or can get it with reasonable efforts.
486+
487+ You may not impose any further restrictions on the exercise of the
488+rights granted or affirmed under this License. For example, you may
489+not impose a license fee, royalty, or other charge for exercise of
490+rights granted under this License, and you may not initiate litigation
491+(including a cross-claim or counterclaim in a lawsuit) alleging that
492+any patent claim is infringed by making, using, selling, offering for
493+sale, or importing the Program or any portion of it.
494+
495+ 11. Patents.
496+
497+ A "contributor" is a copyright holder who authorizes use under this
498+License of the Program or a work on which the Program is based. The
499+work thus licensed is called the contributor's "contributor version".
500+
501+ A contributor's "essential patent claims" are all patent claims
502+owned or controlled by the contributor, whether already acquired or
503+hereafter acquired, that would be infringed by some manner, permitted
504+by this License, of making, using, or selling its contributor version,
505+but do not include claims that would be infringed only as a
506+consequence of further modification of the contributor version. For
507+purposes of this definition, "control" includes the right to grant
508+patent sublicenses in a manner consistent with the requirements of
509+this License.
510+
511+ Each contributor grants you a non-exclusive, worldwide, royalty-free
512+patent license under the contributor's essential patent claims, to
513+make, use, sell, offer for sale, import and otherwise run, modify and
514+propagate the contents of its contributor version.
515+
516+ In the following three paragraphs, a "patent license" is any express
517+agreement or commitment, however denominated, not to enforce a patent
518+(such as an express permission to practice a patent or covenant not to
519+sue for patent infringement). To "grant" such a patent license to a
520+party means to make such an agreement or commitment not to enforce a
521+patent against the party.
522+
523+ If you convey a covered work, knowingly relying on a patent license,
524+and the Corresponding Source of the work is not available for anyone
525+to copy, free of charge and under the terms of this License, through a
526+publicly available network server or other readily accessible means,
527+then you must either (1) cause the Corresponding Source to be so
528+available, or (2) arrange to deprive yourself of the benefit of the
529+patent license for this particular work, or (3) arrange, in a manner
530+consistent with the requirements of this License, to extend the patent
531+license to downstream recipients. "Knowingly relying" means you have
532+actual knowledge that, but for the patent license, your conveying the
533+covered work in a country, or your recipient's use of the covered work
534+in a country, would infringe one or more identifiable patents in that
535+country that you have reason to believe are valid.
536+
537+ If, pursuant to or in connection with a single transaction or
538+arrangement, you convey, or propagate by procuring conveyance of, a
539+covered work, and grant a patent license to some of the parties
540+receiving the covered work authorizing them to use, propagate, modify
541+or convey a specific copy of the covered work, then the patent license
542+you grant is automatically extended to all recipients of the covered
543+work and works based on it.
544+
545+ A patent license is "discriminatory" if it does not include within
546+the scope of its coverage, prohibits the exercise of, or is
547+conditioned on the non-exercise of one or more of the rights that are
548+specifically granted under this License. You may not convey a covered
549+work if you are a party to an arrangement with a third party that is
550+in the business of distributing software, under which you make payment
551+to the third party based on the extent of your activity of conveying
552+the work, and under which the third party grants, to any of the
553+parties who would receive the covered work from you, a discriminatory
554+patent license (a) in connection with copies of the covered work
555+conveyed by you (or copies made from those copies), or (b) primarily
556+for and in connection with specific products or compilations that
557+contain the covered work, unless you entered into that arrangement,
558+or that patent license was granted, prior to 28 March 2007.
559+
560+ Nothing in this License shall be construed as excluding or limiting
561+any implied license or other defenses to infringement that may
562+otherwise be available to you under applicable patent law.
563+
564+ 12. No Surrender of Others' Freedom.
565+
566+ If conditions are imposed on you (whether by court order, agreement or
567+otherwise) that contradict the conditions of this License, they do not
568+excuse you from the conditions of this License. If you cannot convey a
569+covered work so as to satisfy simultaneously your obligations under this
570+License and any other pertinent obligations, then as a consequence you may
571+not convey it at all. For example, if you agree to terms that obligate you
572+to collect a royalty for further conveying from those to whom you convey
573+the Program, the only way you could satisfy both those terms and this
574+License would be to refrain entirely from conveying the Program.
575+
576+ 13. Use with the GNU Affero General Public License.
577+
578+ Notwithstanding any other provision of this License, you have
579+permission to link or combine any covered work with a work licensed
580+under version 3 of the GNU Affero General Public License into a single
581+combined work, and to convey the resulting work. The terms of this
582+License will continue to apply to the part which is the covered work,
583+but the special requirements of the GNU Affero General Public License,
584+section 13, concerning interaction through a network will apply to the
585+combination as such.
586+
587+ 14. Revised Versions of this License.
588+
589+ The Free Software Foundation may publish revised and/or new versions of
590+the GNU General Public License from time to time. Such new versions will
591+be similar in spirit to the present version, but may differ in detail to
592+address new problems or concerns.
593+
594+ Each version is given a distinguishing version number. If the
595+Program specifies that a certain numbered version of the GNU General
596+Public License "or any later version" applies to it, you have the
597+option of following the terms and conditions either of that numbered
598+version or of any later version published by the Free Software
599+Foundation. If the Program does not specify a version number of the
600+GNU General Public License, you may choose any version ever published
601+by the Free Software Foundation.
602+
603+ If the Program specifies that a proxy can decide which future
604+versions of the GNU General Public License can be used, that proxy's
605+public statement of acceptance of a version permanently authorizes you
606+to choose that version for the Program.
607+
608+ Later license versions may give you additional or different
609+permissions. However, no additional obligations are imposed on any
610+author or copyright holder as a result of your choosing to follow a
611+later version.
612+
613+ 15. Disclaimer of Warranty.
614+
615+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
616+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
617+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
618+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
619+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
620+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
621+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
622+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
623+
624+ 16. Limitation of Liability.
625+
626+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
627+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
628+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
629+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
630+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
631+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
632+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
633+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
634+SUCH DAMAGES.
635+
636+ 17. Interpretation of Sections 15 and 16.
637+
638+ If the disclaimer of warranty and limitation of liability provided
639+above cannot be given local legal effect according to their terms,
640+reviewing courts shall apply local law that most closely approximates
641+an absolute waiver of all civil liability in connection with the
642+Program, unless a warranty or assumption of liability accompanies a
643+copy of the Program in return for a fee.
644+
645+ END OF TERMS AND CONDITIONS
646+
647+ How to Apply These Terms to Your New Programs
648+
649+ If you develop a new program, and you want it to be of the greatest
650+possible use to the public, the best way to achieve this is to make it
651+free software which everyone can redistribute and change under these terms.
652+
653+ To do so, attach the following notices to the program. It is safest
654+to attach them to the start of each source file to most effectively
655+state the exclusion of warranty; and each file should have at least
656+the "copyright" line and a pointer to where the full notice is found.
657+
658+ <one line to give the program's name and a brief idea of what it does.>
659+ Copyright (C) <year> <name of author>
660+
661+ This program is free software: you can redistribute it and/or modify
662+ it under the terms of the GNU General Public License as published by
663+ the Free Software Foundation, either version 3 of the License, or
664+ (at your option) any later version.
665+
666+ This program is distributed in the hope that it will be useful,
667+ but WITHOUT ANY WARRANTY; without even the implied warranty of
668+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
669+ GNU General Public License for more details.
670+
671+ You should have received a copy of the GNU General Public License
672+ along with this program. If not, see <http://www.gnu.org/licenses/>.
673+
674+Also add information on how to contact you by electronic and paper mail.
675+
676+ If the program does terminal interaction, make it output a short
677+notice like this when it starts in an interactive mode:
678+
679+ <program> Copyright (C) <year> <name of author>
680+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
681+ This is free software, and you are welcome to redistribute it
682+ under certain conditions; type `show c' for details.
683+
684+The hypothetical commands `show w' and `show c' should show the appropriate
685+parts of the General Public License. Of course, your program's commands
686+might be different; for a GUI interface, you would use an "about box".
687+
688+ You should also get your employer (if you work as a programmer) or school,
689+if any, to sign a "copyright disclaimer" for the program, if necessary.
690+For more information on this, and how to apply and follow the GNU GPL, see
691+<http://www.gnu.org/licenses/>.
692+
693+ The GNU General Public License does not permit incorporating your program
694+into proprietary programs. If your program is a subroutine library, you
695+may consider it more useful to permit linking proprietary applications with
696+the library. If this is what you want to do, use the GNU Lesser General
697+Public License instead of this License. But first, please read
698+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
699
700=== added file 'COPYING.LGPL'
701--- COPYING.LGPL 1970-01-01 00:00:00 +0000
702+++ COPYING.LGPL 2016-11-25 05:01:23 +0000
703@@ -0,0 +1,165 @@
704+ GNU LESSER GENERAL PUBLIC LICENSE
705+ Version 3, 29 June 2007
706+
707+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
708+ Everyone is permitted to copy and distribute verbatim copies
709+ of this license document, but changing it is not allowed.
710+
711+
712+ This version of the GNU Lesser General Public License incorporates
713+the terms and conditions of version 3 of the GNU General Public
714+License, supplemented by the additional permissions listed below.
715+
716+ 0. Additional Definitions.
717+
718+ As used herein, "this License" refers to version 3 of the GNU Lesser
719+General Public License, and the "GNU GPL" refers to version 3 of the GNU
720+General Public License.
721+
722+ "The Library" refers to a covered work governed by this License,
723+other than an Application or a Combined Work as defined below.
724+
725+ An "Application" is any work that makes use of an interface provided
726+by the Library, but which is not otherwise based on the Library.
727+Defining a subclass of a class defined by the Library is deemed a mode
728+of using an interface provided by the Library.
729+
730+ A "Combined Work" is a work produced by combining or linking an
731+Application with the Library. The particular version of the Library
732+with which the Combined Work was made is also called the "Linked
733+Version".
734+
735+ The "Minimal Corresponding Source" for a Combined Work means the
736+Corresponding Source for the Combined Work, excluding any source code
737+for portions of the Combined Work that, considered in isolation, are
738+based on the Application, and not on the Linked Version.
739+
740+ The "Corresponding Application Code" for a Combined Work means the
741+object code and/or source code for the Application, including any data
742+and utility programs needed for reproducing the Combined Work from the
743+Application, but excluding the System Libraries of the Combined Work.
744+
745+ 1. Exception to Section 3 of the GNU GPL.
746+
747+ You may convey a covered work under sections 3 and 4 of this License
748+without being bound by section 3 of the GNU GPL.
749+
750+ 2. Conveying Modified Versions.
751+
752+ If you modify a copy of the Library, and, in your modifications, a
753+facility refers to a function or data to be supplied by an Application
754+that uses the facility (other than as an argument passed when the
755+facility is invoked), then you may convey a copy of the modified
756+version:
757+
758+ a) under this License, provided that you make a good faith effort to
759+ ensure that, in the event an Application does not supply the
760+ function or data, the facility still operates, and performs
761+ whatever part of its purpose remains meaningful, or
762+
763+ b) under the GNU GPL, with none of the additional permissions of
764+ this License applicable to that copy.
765+
766+ 3. Object Code Incorporating Material from Library Header Files.
767+
768+ The object code form of an Application may incorporate material from
769+a header file that is part of the Library. You may convey such object
770+code under terms of your choice, provided that, if the incorporated
771+material is not limited to numerical parameters, data structure
772+layouts and accessors, or small macros, inline functions and templates
773+(ten or fewer lines in length), you do both of the following:
774+
775+ a) Give prominent notice with each copy of the object code that the
776+ Library is used in it and that the Library and its use are
777+ covered by this License.
778+
779+ b) Accompany the object code with a copy of the GNU GPL and this license
780+ document.
781+
782+ 4. Combined Works.
783+
784+ You may convey a Combined Work under terms of your choice that,
785+taken together, effectively do not restrict modification of the
786+portions of the Library contained in the Combined Work and reverse
787+engineering for debugging such modifications, if you also do each of
788+the following:
789+
790+ a) Give prominent notice with each copy of the Combined Work that
791+ the Library is used in it and that the Library and its use are
792+ covered by this License.
793+
794+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
795+ document.
796+
797+ c) For a Combined Work that displays copyright notices during
798+ execution, include the copyright notice for the Library among
799+ these notices, as well as a reference directing the user to the
800+ copies of the GNU GPL and this license document.
801+
802+ d) Do one of the following:
803+
804+ 0) Convey the Minimal Corresponding Source under the terms of this
805+ License, and the Corresponding Application Code in a form
806+ suitable for, and under terms that permit, the user to
807+ recombine or relink the Application with a modified version of
808+ the Linked Version to produce a modified Combined Work, in the
809+ manner specified by section 6 of the GNU GPL for conveying
810+ Corresponding Source.
811+
812+ 1) Use a suitable shared library mechanism for linking with the
813+ Library. A suitable mechanism is one that (a) uses at run time
814+ a copy of the Library already present on the user's computer
815+ system, and (b) will operate properly with a modified version
816+ of the Library that is interface-compatible with the Linked
817+ Version.
818+
819+ e) Provide Installation Information, but only if you would otherwise
820+ be required to provide such information under section 6 of the
821+ GNU GPL, and only to the extent that such information is
822+ necessary to install and execute a modified version of the
823+ Combined Work produced by recombining or relinking the
824+ Application with a modified version of the Linked Version. (If
825+ you use option 4d0, the Installation Information must accompany
826+ the Minimal Corresponding Source and Corresponding Application
827+ Code. If you use option 4d1, you must provide the Installation
828+ Information in the manner specified by section 6 of the GNU GPL
829+ for conveying Corresponding Source.)
830+
831+ 5. Combined Libraries.
832+
833+ You may place library facilities that are a work based on the
834+Library side by side in a single library together with other library
835+facilities that are not Applications and are not covered by this
836+License, and convey such a combined library under terms of your
837+choice, if you do both of the following:
838+
839+ a) Accompany the combined library with a copy of the same work based
840+ on the Library, uncombined with any other library facilities,
841+ conveyed under the terms of this License.
842+
843+ b) Give prominent notice with the combined library that part of it
844+ is a work based on the Library, and explaining where to find the
845+ accompanying uncombined form of the same work.
846+
847+ 6. Revised Versions of the GNU Lesser General Public License.
848+
849+ The Free Software Foundation may publish revised and/or new versions
850+of the GNU Lesser General Public License from time to time. Such new
851+versions will be similar in spirit to the present version, but may
852+differ in detail to address new problems or concerns.
853+
854+ Each version is given a distinguishing version number. If the
855+Library as you received it specifies that a certain numbered version
856+of the GNU Lesser General Public License "or any later version"
857+applies to it, you have the option of following the terms and
858+conditions either of that published version or of any later version
859+published by the Free Software Foundation. If the Library as you
860+received it does not specify a version number of the GNU Lesser
861+General Public License, you may choose any version of the GNU Lesser
862+General Public License ever published by the Free Software Foundation.
863+
864+ If the Library as you received it specifies that a proxy can decide
865+whether future versions of the GNU Lesser General Public License shall
866+apply, that proxy's public statement of acceptance of any version is
867+permanent authorization for you to choose that version for the
868+Library.
869
870=== added directory 'data'
871=== added file 'data/CMakeLists.txt'
872--- data/CMakeLists.txt 1970-01-01 00:00:00 +0000
873+++ data/CMakeLists.txt 2016-11-25 05:01:23 +0000
874@@ -0,0 +1,17 @@
875+# OnlineAccounts support files
876+install(FILES storage-provider-owncloud.service
877+ DESTINATION "${CMAKE_INSTALL_DATADIR}/accounts/services")
878+install(FILES storage-provider-owncloud.application
879+ DESTINATION "${CMAKE_INSTALL_DATADIR}/accounts/applications")
880+install(FILES storage-provider-owncloud.desktop
881+ DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
882+
883+# D-Bus serice activation
884+configure_file(
885+ com.canonical.StorageFramework.Provider.OwnCloud.service.in
886+ com.canonical.StorageFramework.Provider.OwnCloud.service
887+)
888+install(
889+ FILES ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.StorageFramework.Provider.OwnCloud.service
890+ DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services
891+)
892
893=== added file 'data/com.canonical.StorageFramework.Provider.OwnCloud.service.in'
894--- data/com.canonical.StorageFramework.Provider.OwnCloud.service.in 1970-01-01 00:00:00 +0000
895+++ data/com.canonical.StorageFramework.Provider.OwnCloud.service.in 2016-11-25 05:01:23 +0000
896@@ -0,0 +1,3 @@
897+[D-BUS Service]
898+Name=com.canonical.StorageFramework.Provider.OwnCloud
899+Exec=@CMAKE_INSTALL_FULL_LIBDIR@/@PROJECT_NAME@/storage-provider-owncloud
900
901=== added file 'data/storage-provider-owncloud.application'
902--- data/storage-provider-owncloud.application 1970-01-01 00:00:00 +0000
903+++ data/storage-provider-owncloud.application 2016-11-25 05:01:23 +0000
904@@ -0,0 +1,12 @@
905+<?xml version="1.0" encoding="UTF-8" ?>
906+<!-- -*- mode: nxml -*- -->
907+<application id="storage-provider-owncloud">
908+ <description>OwnCloud storage provider</description>
909+ <desktop-entry>storage-provider-owncloud.desktop</desktop-entry>
910+
911+ <services>
912+ <service id="storage-provider-owncloud">
913+ <description>Access your Owncloud documents</description>
914+ </service>
915+ </services>
916+</application>
917
918=== added file 'data/storage-provider-owncloud.desktop'
919--- data/storage-provider-owncloud.desktop 1970-01-01 00:00:00 +0000
920+++ data/storage-provider-owncloud.desktop 2016-11-25 05:01:23 +0000
921@@ -0,0 +1,5 @@
922+[Desktop Entry]
923+Type=Application
924+NoDisplay=true
925+Name=OwnCloud storage provider
926+Icon=online-accounts-owncloud
927
928=== added file 'data/storage-provider-owncloud.service'
929--- data/storage-provider-owncloud.service 1970-01-01 00:00:00 +0000
930+++ data/storage-provider-owncloud.service 2016-11-25 05:01:23 +0000
931@@ -0,0 +1,8 @@
932+<?xml version="1.0" encoding="UTF-8" ?>
933+<!-- -*- mode: nxml -*- -->
934+<service id="storage-provider-owncloud">
935+ <type>storage-provider</type>
936+ <name>OwnCloud</name>
937+ <icon>online-accounts-owncloud</icon>
938+ <provider>owncloud</provider>
939+</service>
940
941=== modified file 'debian/control'
942--- debian/control 2016-09-27 07:08:19 +0000
943+++ debian/control 2016-11-25 05:01:23 +0000
944@@ -1,8 +1,8 @@
945 Source: storage-provider-webdav
946-Section: ???
947+Section: net
948 Priority: optional
949-Maintainer: Ubuntu Core Developers <ubuntu-devel-discuss@lists.ubuntu.com>
950-Standards-Version: 3.9.6
951+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
952+Standards-Version: 3.9.8
953 Build-Depends: cmake,
954 cmake-extras (>= 0.4),
955 debhelper (>= 9),
956@@ -12,8 +12,9 @@
957 libgtest-dev,
958 libonline-accounts-qt-dev,
959 libqtdbustest1-dev,
960- storage-framework-provider-dev,
961- storage-framework-client-dev,
962+ libstorage-framework-qt-client-2-0,
963+ storage-framework-provider-dev (>= 0.2),
964+ storage-framework-client-dev (>= 0.2),
965 php-cli | php5-cli <!nocheck>,
966 php-sabre-dav <!nocheck>,
967 pkg-config,
968@@ -31,8 +32,8 @@
969 Architecture: any
970 Multi-Arch: foreign
971 Pre-Depends: ${misc:Pre-Depends},
972-Depends: ${misc:Depends},
973+Depends: account-plugin-owncloud,
974+ ${misc:Depends},
975 ${shlibs:Depends},
976- account-plugin-owncloud
977 Description: Owncloud plugin for Storage Framework
978 A Storage Framework plugin providing access to Owncloud servers.
979
980=== added file 'debian/copyright'
981--- debian/copyright 1970-01-01 00:00:00 +0000
982+++ debian/copyright 2016-11-25 05:01:23 +0000
983@@ -0,0 +1,36 @@
984+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
985+Upstream-Name: storage-provider-webdav
986+Source: https://launchpad.net/storage-provider-webdav
987+
988+Files: *
989+Copyright: 2016 Canonical Ltd.
990+License: GPL-3
991+ This program is free software: you can redistribute it and/or modify
992+ it under the terms of the GNU General Public License version 3 as
993+ published by the Free Software Foundation.
994+ .
995+ This program is distributed in the hope that it will be useful,
996+ but WITHOUT ANY WARRANTY; without even the implied warranty of
997+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
998+ GNU General Public License for more details.
999+ .
1000+ On Debian/Ubuntu systems, the full text of the GPL v3 can be found in
1001+ `/usr/share/common-licenses/GPL-3'
1002+
1003+Files: tests/utils/DBusEnvironment.*
1004+ tests/utils/ProviderEnvironment.*
1005+ tests/utils/fake-onlye-accounts-daemon.py
1006+Copyright: 2016 Canonical Ltd.
1007+License: LGPL-3
1008+ This program is free software: you can redistribute it and/or modify
1009+ it under the terms of version 3 of the GNU Lesser General Public
1010+ License as published by the Free Software Foundation.
1011+ .
1012+ This program is distributed in the hope that it will be useful,
1013+ but WITHOUT ANY WARRANTY; without even the implied warranty of
1014+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1015+ GNU Lesser General Public License for more details.
1016+ .
1017+ On Debian systems, the full text of the GNU Lesser General Public
1018+ License version 3 can be found in the file
1019+ `/usr/share/common-licenses/LGPL-3'
1020
1021=== modified file 'src/CMakeLists.txt'
1022--- src/CMakeLists.txt 2016-09-27 07:08:19 +0000
1023+++ src/CMakeLists.txt 2016-11-25 05:01:23 +0000
1024@@ -1,9 +1,20 @@
1025
1026 add_library(dav-provider-lib STATIC
1027 DavProvider.cpp
1028- RootsHandler.cpp
1029+ DavDownloadJob.cpp
1030+ DavUploadJob.cpp
1031 MultiStatusParser.cpp
1032+ http_error.cpp
1033 item_id.cpp
1034+ CopyMoveHandler.cpp
1035+ CreateFolderHandler.cpp
1036+ DeleteHandler.cpp
1037+ PropFindHandler.cpp
1038+ ListHandler.cpp
1039+ LookupHandler.cpp
1040+ MetadataHandler.cpp
1041+ RetrieveMetadataHandler.cpp
1042+ RootsHandler.cpp
1043 )
1044 target_compile_options(dav-provider-lib PUBLIC
1045 ${SF_PROVIDER_CFLAGS}
1046@@ -28,3 +39,8 @@
1047 target_link_libraries(storage-provider-owncloud
1048 dav-provider-lib
1049 )
1050+
1051+install(
1052+ TARGETS storage-provider-owncloud
1053+ RUNTIME DESTINATION "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}"
1054+)
1055
1056=== added file 'src/CopyMoveHandler.cpp'
1057--- src/CopyMoveHandler.cpp 1970-01-01 00:00:00 +0000
1058+++ src/CopyMoveHandler.cpp 2016-11-25 05:01:23 +0000
1059@@ -0,0 +1,85 @@
1060+/*
1061+ * Copyright (C) 2016 Canonical Ltd.
1062+ *
1063+ * This program is free software: you can redistribute it and/or modify
1064+ * it under the terms of the GNU General Public License version 3 as
1065+ * published by the Free Software Foundation.
1066+ *
1067+ * This program is distributed in the hope that it will be useful,
1068+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1069+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1070+ * GNU General Public License for more details.
1071+ *
1072+ * You should have received a copy of the GNU General Public License
1073+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1074+ *
1075+ * Authored by: James Henstridge <james.henstridge@canonical.com>
1076+ */
1077+
1078+#include "CopyMoveHandler.h"
1079+#include "RetrieveMetadataHandler.h"
1080+#include "item_id.h"
1081+#include "http_error.h"
1082+
1083+using namespace std;
1084+using namespace unity::storage::provider;
1085+
1086+CopyMoveHandler::CopyMoveHandler(shared_ptr<DavProvider> const& provider,
1087+ string const& item_id,
1088+ string const& new_parent_id,
1089+ string const& new_name,
1090+ bool copy,
1091+ Context const& ctx)
1092+ : provider_(provider), item_id_(item_id),
1093+ new_item_id_(make_child_id(new_parent_id, new_name, is_folder(item_id))),
1094+ context_(ctx)
1095+{
1096+ QUrl const base_url = provider->base_url(ctx);
1097+ QNetworkRequest request(id_to_url(item_id_, base_url));
1098+ request.setRawHeader(QByteArrayLiteral("Destination"),
1099+ id_to_url(new_item_id_, base_url).toEncoded());
1100+ // Error out of the operation would overwrite an existing resource.
1101+ request.setRawHeader(QByteArrayLiteral("Overwrite"),
1102+ QByteArrayLiteral("F"));
1103+
1104+ reply_.reset(provider->send_request(
1105+ request, copy ? QByteArrayLiteral("COPY") : QByteArrayLiteral("MOVE"),
1106+ nullptr, ctx));
1107+ connect(reply_.get(), &QNetworkReply::finished,
1108+ this, &CopyMoveHandler::onFinished);
1109+}
1110+
1111+CopyMoveHandler::~CopyMoveHandler() = default;
1112+
1113+boost::future<Item> CopyMoveHandler::get_future()
1114+{
1115+ return promise_.get_future();
1116+}
1117+
1118+void CopyMoveHandler::onFinished()
1119+{
1120+ auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
1121+
1122+ if (status != 201 && status != 204)
1123+ {
1124+ promise_.set_exception(
1125+ translate_http_error(reply_.get(), QByteArray(), item_id_));
1126+ deleteLater();
1127+ return;
1128+ }
1129+
1130+ metadata_.reset(
1131+ new RetrieveMetadataHandler(
1132+ provider_, new_item_id_, context_,
1133+ [this](Item const& item, boost::exception_ptr const& error) {
1134+ if (error)
1135+ {
1136+ promise_.set_exception(error);
1137+ }
1138+ else
1139+ {
1140+ promise_.set_value(item);
1141+ }
1142+ deleteLater();
1143+ }));
1144+}
1145
1146=== added file 'src/CopyMoveHandler.h'
1147--- src/CopyMoveHandler.h 1970-01-01 00:00:00 +0000
1148+++ src/CopyMoveHandler.h 2016-11-25 05:01:23 +0000
1149@@ -0,0 +1,57 @@
1150+/*
1151+ * Copyright (C) 2016 Canonical Ltd.
1152+ *
1153+ * This program is free software: you can redistribute it and/or modify
1154+ * it under the terms of the GNU General Public License version 3 as
1155+ * published by the Free Software Foundation.
1156+ *
1157+ * This program is distributed in the hope that it will be useful,
1158+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1159+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1160+ * GNU General Public License for more details.
1161+ *
1162+ * You should have received a copy of the GNU General Public License
1163+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1164+ *
1165+ * Authored by: James Henstridge <james.henstridge@canonical.com>
1166+ */
1167+
1168+#pragma once
1169+
1170+#include <QBuffer>
1171+#include <QObject>
1172+#include <QNetworkReply>
1173+#include <unity/storage/provider/ProviderBase.h>
1174+
1175+#include <memory>
1176+
1177+class DavProvider;
1178+class RetrieveMetadataHandler;
1179+
1180+class CopyMoveHandler : public QObject {
1181+ Q_OBJECT
1182+public:
1183+ CopyMoveHandler(std::shared_ptr<DavProvider> const& provider,
1184+ std::string const& item_id,
1185+ std::string const& new_parent_id,
1186+ std::string const& new_name,
1187+ bool copy,
1188+ unity::storage::provider::Context const& ctx);
1189+ ~CopyMoveHandler();
1190+
1191+ boost::future<unity::storage::provider::Item> get_future();
1192+
1193+private Q_SLOTS:
1194+ void onFinished();
1195+
1196+private:
1197+ boost::promise<unity::storage::provider::Item> promise_;
1198+
1199+ std::shared_ptr<DavProvider> const provider_;
1200+ std::string const item_id_;
1201+ std::string const new_item_id_;
1202+ unity::storage::provider::Context const context_;
1203+
1204+ std::unique_ptr<QNetworkReply> reply_;
1205+ std::unique_ptr<RetrieveMetadataHandler> metadata_;
1206+};
1207
1208=== added file 'src/CreateFolderHandler.cpp'
1209--- src/CreateFolderHandler.cpp 1970-01-01 00:00:00 +0000
1210+++ src/CreateFolderHandler.cpp 2016-11-25 05:01:23 +0000
1211@@ -0,0 +1,75 @@
1212+/*
1213+ * Copyright (C) 2016 Canonical Ltd.
1214+ *
1215+ * This program is free software: you can redistribute it and/or modify
1216+ * it under the terms of the GNU General Public License version 3 as
1217+ * published by the Free Software Foundation.
1218+ *
1219+ * This program is distributed in the hope that it will be useful,
1220+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1221+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1222+ * GNU General Public License for more details.
1223+ *
1224+ * You should have received a copy of the GNU General Public License
1225+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1226+ *
1227+ * Authored by: James Henstridge <james.henstridge@canonical.com>
1228+ */
1229+
1230+#include "CreateFolderHandler.h"
1231+#include "RetrieveMetadataHandler.h"
1232+#include "item_id.h"
1233+#include "http_error.h"
1234+
1235+using namespace std;
1236+using namespace unity::storage::provider;
1237+
1238+CreateFolderHandler::CreateFolderHandler(shared_ptr<DavProvider> const& provider,
1239+ string const& parent_id,
1240+ string const& name,
1241+ Context const& ctx)
1242+ : provider_(provider), item_id_(make_child_id(parent_id, name, true)),
1243+ context_(ctx)
1244+{
1245+ QUrl const base_url = provider->base_url(ctx);
1246+ QNetworkRequest request(id_to_url(item_id_, base_url));
1247+ reply_.reset(provider->send_request(request, QByteArrayLiteral("MKCOL"),
1248+ nullptr, ctx));
1249+ connect(reply_.get(), &QNetworkReply::finished,
1250+ this, &CreateFolderHandler::onFinished);
1251+}
1252+
1253+CreateFolderHandler::~CreateFolderHandler() = default;
1254+
1255+boost::future<Item> CreateFolderHandler::get_future()
1256+{
1257+ return promise_.get_future();
1258+}
1259+
1260+void CreateFolderHandler::onFinished()
1261+{
1262+ auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
1263+
1264+ if (status != 201)
1265+ {
1266+ promise_.set_exception(
1267+ translate_http_error(reply_.get(), QByteArray(), item_id_));
1268+ deleteLater();
1269+ return;
1270+ }
1271+
1272+ metadata_.reset(
1273+ new RetrieveMetadataHandler(
1274+ provider_, item_id_, context_,
1275+ [this](Item const& item, boost::exception_ptr const& error) {
1276+ if (error)
1277+ {
1278+ promise_.set_exception(error);
1279+ }
1280+ else
1281+ {
1282+ promise_.set_value(item);
1283+ }
1284+ deleteLater();
1285+ }));
1286+}
1287
1288=== added file 'src/CreateFolderHandler.h'
1289--- src/CreateFolderHandler.h 1970-01-01 00:00:00 +0000
1290+++ src/CreateFolderHandler.h 2016-11-25 05:01:23 +0000
1291@@ -0,0 +1,53 @@
1292+/*
1293+ * Copyright (C) 2016 Canonical Ltd.
1294+ *
1295+ * This program is free software: you can redistribute it and/or modify
1296+ * it under the terms of the GNU General Public License version 3 as
1297+ * published by the Free Software Foundation.
1298+ *
1299+ * This program is distributed in the hope that it will be useful,
1300+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1301+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1302+ * GNU General Public License for more details.
1303+ *
1304+ * You should have received a copy of the GNU General Public License
1305+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1306+ *
1307+ * Authored by: James Henstridge <james.henstridge@canonical.com>
1308+ */
1309+
1310+#pragma once
1311+
1312+#include <QBuffer>
1313+#include <QObject>
1314+#include <QNetworkReply>
1315+#include <unity/storage/provider/ProviderBase.h>
1316+
1317+#include <memory>
1318+
1319+class DavProvider;
1320+class RetrieveMetadataHandler;
1321+
1322+class CreateFolderHandler : public QObject {
1323+ Q_OBJECT
1324+public:
1325+ CreateFolderHandler(std::shared_ptr<DavProvider> const& provider,
1326+ std::string const& parent_id, std::string const& name,
1327+ unity::storage::provider::Context const& ctx);
1328+ ~CreateFolderHandler();
1329+
1330+ boost::future<unity::storage::provider::Item> get_future();
1331+
1332+private Q_SLOTS:
1333+ void onFinished();
1334+
1335+private:
1336+ boost::promise<unity::storage::provider::Item> promise_;
1337+
1338+ std::shared_ptr<DavProvider> const provider_;
1339+ std::string const item_id_;
1340+ unity::storage::provider::Context const context_;
1341+
1342+ std::unique_ptr<QNetworkReply> reply_;
1343+ std::unique_ptr<RetrieveMetadataHandler> metadata_;
1344+};
1345
1346=== added file 'src/DavDownloadJob.cpp'
1347--- src/DavDownloadJob.cpp 1970-01-01 00:00:00 +0000
1348+++ src/DavDownloadJob.cpp 2016-11-25 05:01:23 +0000
1349@@ -0,0 +1,215 @@
1350+/*
1351+ * Copyright (C) 2016 Canonical Ltd.
1352+ *
1353+ * This program is free software: you can redistribute it and/or modify
1354+ * it under the terms of the GNU General Public License version 3 as
1355+ * published by the Free Software Foundation.
1356+ *
1357+ * This program is distributed in the hope that it will be useful,
1358+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1359+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1360+ * GNU General Public License for more details.
1361+ *
1362+ * You should have received a copy of the GNU General Public License
1363+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1364+ *
1365+ * Authored by: James Henstridge <james.henstridge@canonical.com>
1366+ */
1367+
1368+#include "DavDownloadJob.h"
1369+#include "DavProvider.h"
1370+#include "RetrieveMetadataHandler.h"
1371+#include "item_id.h"
1372+#include "http_error.h"
1373+
1374+#include <unity/storage/provider/Exceptions.h>
1375+
1376+#include <unistd.h>
1377+#include <cassert>
1378+#include <cstdint>
1379+#include <string>
1380+
1381+using namespace std;
1382+using namespace unity::storage::provider;
1383+using unity::storage::ItemType;
1384+
1385+namespace
1386+{
1387+
1388+string make_download_id()
1389+{
1390+ static int counter = 0;
1391+ return to_string(counter++);
1392+}
1393+
1394+constexpr int CHUNK_SIZE = 64 * 1024;
1395+
1396+}
1397+
1398+DavDownloadJob::DavDownloadJob(shared_ptr<DavProvider> const& provider,
1399+ string const& item_id,
1400+ string const& match_etag,
1401+ Context const& ctx)
1402+ : QObject(), DownloadJob(make_download_id()), provider_(provider),
1403+ item_id_(item_id)
1404+{
1405+ QUrl base_url = provider->base_url(ctx);
1406+ QNetworkRequest request(id_to_url(item_id, base_url));
1407+ if (!match_etag.empty())
1408+ {
1409+ request.setRawHeader(QByteArrayLiteral("If-Match"),
1410+ QByteArray::fromStdString(match_etag));
1411+ }
1412+
1413+ reply_.reset(provider->send_request(
1414+ request, QByteArrayLiteral("GET"), nullptr, ctx));
1415+ assert(reply_.get() != nullptr);
1416+ reply_->setReadBufferSize(CHUNK_SIZE);
1417+ connect(reply_.get(), &QNetworkReply::finished,
1418+ this, &DavDownloadJob::onReplyFinished);
1419+ connect(reply_.get(), &QIODevice::readyRead,
1420+ this, &DavDownloadJob::onReplyReadyRead);
1421+ connect(reply_.get(), &QIODevice::readChannelFinished,
1422+ this, &DavDownloadJob::onReplyReadChannelFinished);
1423+ writer_.setSocketDescriptor(
1424+ dup(write_socket()), QLocalSocket::ConnectedState, QIODevice::WriteOnly);
1425+ connect(&writer_, &QIODevice::bytesWritten,
1426+ this, &DavDownloadJob::onSocketBytesWritten);
1427+}
1428+
1429+DavDownloadJob::~DavDownloadJob() = default;
1430+
1431+void DavDownloadJob::onReplyFinished()
1432+{
1433+ if (!seen_header_ || is_error_)
1434+ {
1435+ try
1436+ {
1437+ boost::rethrow_exception(
1438+ translate_http_error(reply_.get(), error_body_, item_id_));
1439+ }
1440+ catch (...)
1441+ {
1442+ handle_error(std::current_exception());
1443+ }
1444+ }
1445+}
1446+
1447+void DavDownloadJob::onReplyReadyRead()
1448+{
1449+ if (!seen_header_)
1450+ {
1451+ seen_header_ = true;
1452+ auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
1453+ if (status != 200)
1454+ {
1455+ is_error_ = true;
1456+ }
1457+ }
1458+
1459+ if (is_error_)
1460+ {
1461+ if (error_body_.size() < MAX_ERROR_BODY_LENGTH)
1462+ {
1463+ error_body_.append(reply_->readAll());
1464+ }
1465+ else
1466+ {
1467+ reply_->close();
1468+ }
1469+ }
1470+ else
1471+ {
1472+ maybe_send_chunk();
1473+ }
1474+}
1475+
1476+void DavDownloadJob::onReplyReadChannelFinished()
1477+{
1478+ if (is_error_)
1479+ {
1480+ return;
1481+ }
1482+
1483+ read_channel_finished_ = true;
1484+ maybe_send_chunk();
1485+}
1486+
1487+void DavDownloadJob::onSocketBytesWritten(int64_t bytes)
1488+{
1489+ if (is_error_)
1490+ {
1491+ return;
1492+ }
1493+
1494+ bytes_written_ += bytes;
1495+ maybe_send_chunk();
1496+}
1497+
1498+void DavDownloadJob::maybe_send_chunk()
1499+{
1500+ assert(bytes_written_ <= bytes_read_);
1501+ // If there are outstanding writes, do nothing.
1502+ if (bytes_written_ < bytes_read_)
1503+ {
1504+ return;
1505+ }
1506+ // If there are no bytes available, return.
1507+ if (reply_->bytesAvailable() == 0)
1508+ {
1509+ // If we've reached the end of the input, and all data has been
1510+ // written out, signal completion.
1511+ if (read_channel_finished_ && bytes_written_ == bytes_read_)
1512+ {
1513+ writer_.close();
1514+ report_complete();
1515+ }
1516+ return;
1517+ }
1518+
1519+ char buffer[CHUNK_SIZE];
1520+ int n_read = reply_->read(buffer, CHUNK_SIZE);
1521+ if (n_read < 0)
1522+ {
1523+ handle_error(RemoteCommsException("Failed to read from server: " +
1524+ reply_->errorString().toStdString()));
1525+ return;
1526+ }
1527+ bytes_read_ += n_read;
1528+
1529+ int n_written = writer_.write(buffer, n_read);
1530+ if (n_written < 0)
1531+ {
1532+ handle_error(ResourceException(
1533+ "Error writing to socket: "
1534+ + writer_.errorString().toStdString(), 0));
1535+ return;
1536+ }
1537+}
1538+
1539+void DavDownloadJob::handle_error(StorageException const& exc)
1540+{
1541+ handle_error(std::make_exception_ptr(exc));
1542+}
1543+
1544+void DavDownloadJob::handle_error(std::exception_ptr ep)
1545+{
1546+ is_error_ = true;
1547+ reply_->close();
1548+ writer_.close();
1549+ report_error(ep);
1550+}
1551+
1552+
1553+boost::future<void> DavDownloadJob::cancel()
1554+{
1555+ reply_->abort();
1556+ writer_.close();
1557+ return boost::make_ready_future();
1558+}
1559+
1560+boost::future<void> DavDownloadJob::finish()
1561+{
1562+ return boost::make_exceptional_future<void>(
1563+ LogicException("finish called before all data sent"));
1564+}
1565
1566=== added file 'src/DavDownloadJob.h'
1567--- src/DavDownloadJob.h 1970-01-01 00:00:00 +0000
1568+++ src/DavDownloadJob.h 2016-11-25 05:01:23 +0000
1569@@ -0,0 +1,72 @@
1570+/*
1571+ * Copyright (C) 2016 Canonical Ltd.
1572+ *
1573+ * This program is free software: you can redistribute it and/or modify
1574+ * it under the terms of the GNU General Public License version 3 as
1575+ * published by the Free Software Foundation.
1576+ *
1577+ * This program is distributed in the hope that it will be useful,
1578+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1579+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1580+ * GNU General Public License for more details.
1581+ *
1582+ * You should have received a copy of the GNU General Public License
1583+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1584+ *
1585+ * Authored by: James Henstridge <james.henstridge@canonical.com>
1586+ */
1587+
1588+#pragma once
1589+
1590+#include <QByteArray>
1591+#include <QLocalSocket>
1592+#include <QNetworkReply>
1593+#include <QObject>
1594+#include <QUrl>
1595+#include <unity/storage/provider/Exceptions.h>
1596+#include <unity/storage/provider/ProviderBase.h>
1597+#include <unity/storage/provider/DownloadJob.h>
1598+
1599+#include <cstdint>
1600+#include <memory>
1601+#include <string>
1602+
1603+class DavProvider;
1604+
1605+class DavDownloadJob : public QObject, public unity::storage::provider::DownloadJob
1606+{
1607+ Q_OBJECT
1608+public:
1609+ DavDownloadJob(std::shared_ptr<DavProvider> const& provider,
1610+ std::string const& item_id, std::string const& match_etag,
1611+ unity::storage::provider::Context const& ctx);
1612+ ~DavDownloadJob();
1613+
1614+ boost::future<void> cancel() override;
1615+ boost::future<void> finish() override;
1616+
1617+private Q_SLOTS:
1618+ void onReplyFinished();
1619+ void onReplyReadyRead();
1620+ void onReplyReadChannelFinished();
1621+
1622+ void onSocketBytesWritten(int64_t bytes);
1623+
1624+private:
1625+ void maybe_send_chunk();
1626+ void handle_error(unity::storage::provider::StorageException const& exc);
1627+ void handle_error(std::exception_ptr ep);
1628+
1629+ std::shared_ptr<DavProvider> const provider_;
1630+ std::string const item_id_;
1631+ QLocalSocket writer_;
1632+ std::unique_ptr<QNetworkReply> reply_;
1633+
1634+ bool seen_header_ = false;
1635+ bool read_channel_finished_ = false;
1636+ int64_t bytes_read_ = 0;
1637+ int64_t bytes_written_ = 0;
1638+
1639+ bool is_error_ = false;
1640+ QByteArray error_body_;
1641+};
1642
1643=== modified file 'src/DavProvider.cpp'
1644--- src/DavProvider.cpp 2016-09-22 06:40:07 +0000
1645+++ src/DavProvider.cpp 2016-11-25 05:01:23 +0000
1646@@ -1,12 +1,46 @@
1647+/*
1648+ * Copyright (C) 2016 Canonical Ltd.
1649+ *
1650+ * This program is free software: you can redistribute it and/or modify
1651+ * it under the terms of the GNU General Public License version 3 as
1652+ * published by the Free Software Foundation.
1653+ *
1654+ * This program is distributed in the hope that it will be useful,
1655+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1656+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1657+ * GNU General Public License for more details.
1658+ *
1659+ * You should have received a copy of the GNU General Public License
1660+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1661+ *
1662+ * Authored by: James Henstridge <james.henstridge@canonical.com>
1663+ */
1664+
1665 #include "DavProvider.h"
1666+#include "MultiStatusParser.h"
1667 #include "RootsHandler.h"
1668+#include "ListHandler.h"
1669+#include "LookupHandler.h"
1670+#include "MetadataHandler.h"
1671+#include "DavDownloadJob.h"
1672+#include "DavUploadJob.h"
1673+#include "CreateFolderHandler.h"
1674+#include "DeleteHandler.h"
1675+#include "CopyMoveHandler.h"
1676+#include "item_id.h"
1677
1678+#include <QDateTime>
1679+#include <QDebug>
1680 #include <QNetworkAccessManager>
1681 #include <QNetworkReply>
1682 #include <QNetworkRequest>
1683+#include <unity/storage/common.h>
1684+#include <unity/storage/provider/Exceptions.h>
1685
1686 using namespace std;
1687 using namespace unity::storage::provider;
1688+using namespace unity::storage::metadata;
1689+using unity::storage::ItemType;
1690
1691 DavProvider::DavProvider()
1692 : network_(new QNetworkAccessManager)
1693@@ -15,62 +49,198 @@
1694
1695 DavProvider::~DavProvider() = default;
1696
1697-boost::future<ItemList> DavProvider::roots(Context const& ctx)
1698-{
1699- auto handler = new RootsHandler(*this, ctx);
1700+std::shared_ptr<DavProvider> DavProvider::shared_from_this()
1701+{
1702+ return static_pointer_cast<DavProvider>(ProviderBase::shared_from_this());
1703+}
1704+
1705+boost::future<ItemList> DavProvider::roots(
1706+ vector<string> const& metadata_keys, Context const& ctx)
1707+{
1708+ Q_UNUSED(metadata_keys);
1709+ auto handler = new RootsHandler(shared_from_this(), ctx);
1710 return handler->get_future();
1711 }
1712
1713 boost::future<tuple<ItemList,string>> DavProvider::list(
1714- string const& item_id, string const& page_token, Context const& ctx)
1715+ string const& item_id, string const& page_token,
1716+ vector<string> const& metadata_keys, Context const& ctx)
1717 {
1718+ Q_UNUSED(metadata_keys);
1719+ if (!page_token.empty())
1720+ {
1721+ throw InvalidArgumentException("Invalid paging token: " + page_token);
1722+ }
1723+ auto handler = new ListHandler(shared_from_this(), item_id, ctx);
1724+ return handler->get_future();
1725 }
1726
1727 boost::future<ItemList> DavProvider::lookup(
1728- string const& parent_id, string const& name, Context const& ctx)
1729+ string const& parent_id, string const& name,
1730+ vector<string> const& metadata_keys, Context const& ctx)
1731 {
1732+ Q_UNUSED(metadata_keys);
1733+ string item_id = make_child_id(parent_id, name);
1734+ auto handler = new LookupHandler(shared_from_this(), item_id, ctx);
1735+ return handler->get_future();
1736 }
1737
1738 boost::future<Item> DavProvider::metadata(
1739- string const& item_id, Context const& ctx)
1740+ string const& item_id, vector<string> const& metadata_keys,
1741+ Context const& ctx)
1742 {
1743+ Q_UNUSED(metadata_keys);
1744+ auto handler = new MetadataHandler(shared_from_this(), item_id, ctx);
1745+ return handler->get_future();
1746 }
1747
1748 boost::future<Item> DavProvider::create_folder(
1749- string const& parent_id, string const& name, Context const& ctx)
1750+ string const& parent_id, string const& name,
1751+ vector<string> const& metadata_keys, Context const& ctx)
1752 {
1753+ Q_UNUSED(metadata_keys);
1754+ auto handler = new CreateFolderHandler(
1755+ shared_from_this(), parent_id, name, ctx);
1756+ return handler->get_future();
1757 }
1758
1759 boost::future<unique_ptr<UploadJob>> DavProvider::create_file(
1760 string const& parent_id, string const& name, int64_t size,
1761- string const& content_type, bool allow_overwrite, Context const& ctx)
1762+ string const& content_type, bool allow_overwrite,
1763+ vector<string> const& metadata_keys, Context const& ctx)
1764 {
1765+ Q_UNUSED(metadata_keys);
1766+ string item_id = make_child_id(parent_id, name);
1767+ boost::promise<unique_ptr<UploadJob>> p;
1768+ p.set_value(unique_ptr<UploadJob>(new DavUploadJob(
1769+ shared_from_this(), item_id, size, content_type, allow_overwrite,
1770+ string(), ctx)));
1771+ return p.get_future();
1772 }
1773
1774 boost::future<unique_ptr<UploadJob>> DavProvider::update(
1775 string const& item_id, int64_t size, string const& old_etag,
1776- Context const& ctx)
1777+ vector<string> const& metadata_keys, Context const& ctx)
1778 {
1779+ Q_UNUSED(metadata_keys);
1780+ boost::promise<unique_ptr<UploadJob>> p;
1781+ p.set_value(unique_ptr<UploadJob>(new DavUploadJob(
1782+ shared_from_this(), item_id, size, string(), true, old_etag, ctx)));
1783+ return p.get_future();
1784 }
1785
1786 boost::future<unique_ptr<DownloadJob>> DavProvider::download(
1787- string const& item_id, Context const& ctx)
1788+ string const& item_id, string const& match_etag, Context const& ctx)
1789 {
1790+ boost::promise<unique_ptr<DownloadJob>> p;
1791+ p.set_value(unique_ptr<DownloadJob>(new DavDownloadJob(
1792+ shared_from_this(), item_id, match_etag, ctx)));
1793+ return p.get_future();
1794 }
1795
1796 boost::future<void> DavProvider::delete_item(
1797 string const& item_id, Context const& ctx)
1798 {
1799+ auto handler = new DeleteHandler(shared_from_this(), item_id, ctx);
1800+ return handler->get_future();
1801 }
1802
1803 boost::future<Item> DavProvider::move(
1804 string const& item_id, string const& new_parent_id, string const& new_name,
1805- Context const& ctx)
1806+ vector<string> const& metadata_keys, Context const& ctx)
1807 {
1808+ Q_UNUSED(metadata_keys);
1809+ auto handler = new CopyMoveHandler(
1810+ shared_from_this(), item_id, new_parent_id, new_name, false, ctx);
1811+ return handler->get_future();
1812 }
1813
1814 boost::future<Item> DavProvider::copy(
1815 string const& item_id, string const& new_parent_id, string const& new_name,
1816- Context const& ctx)
1817-{
1818+ vector<string> const& metadata_keys, Context const& ctx)
1819+{
1820+ Q_UNUSED(metadata_keys);
1821+ auto handler = new CopyMoveHandler(
1822+ shared_from_this(), item_id, new_parent_id, new_name, true, ctx);
1823+ return handler->get_future();
1824+}
1825+
1826+Item DavProvider::make_item(QUrl const& href, QUrl const& base_url,
1827+ vector<MultiStatusProperty> const& properties) const
1828+{
1829+ Item item;
1830+ item.item_id = url_to_id(href, base_url);
1831+
1832+ QByteArray path = href.path(QUrl::FullyEncoded |
1833+ QUrl::StripTrailingSlash).toUtf8();
1834+ int pos = path.lastIndexOf('/');
1835+ assert(pos >= 0);
1836+ try
1837+ {
1838+ item.parent_ids.emplace_back(url_to_id(
1839+ href.resolved(QUrl::fromEncoded(path.mid(0, pos+1),
1840+ QUrl::StrictMode)), base_url));
1841+ }
1842+ catch (RemoteCommsException const&)
1843+ {
1844+ // Path is outside of base URL: don't expose.
1845+ }
1846+
1847+ item.name = QUrl::fromPercentEncoding(path.mid(pos+1)).toStdString();
1848+ item.type = ItemType::file;
1849+
1850+ for (const auto& prop : properties)
1851+ {
1852+ if (prop.status != 200)
1853+ {
1854+ // Don't warn about "404 Not Found" properties
1855+ if (prop.status != 404)
1856+ {
1857+ qWarning() << "Got status" << prop.status << "for property"
1858+ << prop.ns << prop.name;
1859+ }
1860+ continue;
1861+ }
1862+ if (prop.ns == "DAV:")
1863+ {
1864+ if (prop.name == "resourcetype")
1865+ {
1866+ if (prop.value == "DAV:collection") {
1867+ item.type = ItemType::folder;
1868+ }
1869+ }
1870+ if (prop.name == "getetag")
1871+ {
1872+ item.etag = prop.value.toStdString();
1873+ }
1874+ else if (prop.name == "getcontentlength")
1875+ {
1876+ item.metadata[SIZE_IN_BYTES] = static_cast<int64_t>(prop.value.toLongLong());
1877+ }
1878+ else if (prop.name == "creationdate")
1879+ {
1880+ auto date = QDateTime::fromString(prop.value, Qt::RFC2822Date);
1881+ if (date.isValid())
1882+ {
1883+ item.metadata[CREATION_TIME] = date.toString(Qt::ISODate).toStdString();
1884+ }
1885+ }
1886+ else if (prop.name == "getlastmodified")
1887+ {
1888+ auto date = QDateTime::fromString(prop.value, Qt::RFC2822Date);
1889+ if (date.isValid())
1890+ {
1891+ item.metadata[LAST_MODIFIED_TIME] = date.toString(Qt::ISODate).toStdString();
1892+ }
1893+ }
1894+ }
1895+ }
1896+
1897+ if (href == base_url)
1898+ {
1899+ item.type = ItemType::root;
1900+ item.name = "Root";
1901+ item.parent_ids.clear();
1902+ }
1903+ return item;
1904 }
1905
1906=== modified file 'src/DavProvider.h'
1907--- src/DavProvider.h 2016-09-22 03:36:22 +0000
1908+++ src/DavProvider.h 2016-11-25 05:01:23 +0000
1909@@ -1,3 +1,21 @@
1910+/*
1911+ * Copyright (C) 2016 Canonical Ltd.
1912+ *
1913+ * This program is free software: you can redistribute it and/or modify
1914+ * it under the terms of the GNU General Public License version 3 as
1915+ * published by the Free Software Foundation.
1916+ *
1917+ * This program is distributed in the hope that it will be useful,
1918+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1919+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1920+ * GNU General Public License for more details.
1921+ *
1922+ * You should have received a copy of the GNU General Public License
1923+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1924+ *
1925+ * Authored by: James Henstridge <james.henstridge@canonical.com>
1926+ */
1927+
1928 #pragma once
1929
1930 #include <unity/storage/provider/ProviderBase.h>
1931@@ -10,6 +28,7 @@
1932 class QNetworkReply;
1933 class QNetworkRequest;
1934 class QUrl;
1935+struct MultiStatusProperty;
1936
1937 class DavProvider : public unity::storage::provider::ProviderBase
1938 {
1939@@ -18,31 +37,39 @@
1940 virtual ~DavProvider();
1941
1942 boost::future<unity::storage::provider::ItemList> roots(
1943+ std::vector<std::string> const& metadata_keys,
1944 unity::storage::provider::Context const& ctx) override;
1945 boost::future<std::tuple<unity::storage::provider::ItemList,std::string>> list(
1946 std::string const& item_id, std::string const& page_token,
1947+ std::vector<std::string> const& metadata_keys,
1948 unity::storage::provider::Context const& ctx) override;
1949 boost::future<unity::storage::provider::ItemList> lookup(
1950 std::string const& parent_id, std::string const& name,
1951+ std::vector<std::string> const& metadata_keys,
1952 unity::storage::provider::Context const& ctx) override;
1953 boost::future<unity::storage::provider::Item> metadata(
1954 std::string const& item_id,
1955+ std::vector<std::string> const& metadata_keys,
1956 unity::storage::provider::Context const& ctx) override;
1957
1958 boost::future<unity::storage::provider::Item> create_folder(
1959 std::string const& parent_id, std::string const& name,
1960+ std::vector<std::string> const& metadata_keys,
1961 unity::storage::provider::Context const& ctx) override;
1962 boost::future<std::unique_ptr<unity::storage::provider::UploadJob>> create_file(
1963 std::string const& parent_id, std::string const& name,
1964 int64_t size, std::string const& content_type, bool allow_overwrite,
1965+ std::vector<std::string> const& metadata_keys,
1966 unity::storage::provider::Context const& ctx) override;
1967 boost::future<std::unique_ptr<unity::storage::provider::UploadJob>> update(
1968 std::string const& item_id, int64_t size,
1969 std::string const& old_etag,
1970+ std::vector<std::string> const& metadata_keys,
1971 unity::storage::provider::Context const& ctx) override;
1972
1973 boost::future<std::unique_ptr<unity::storage::provider::DownloadJob>> download(
1974 std::string const& item_id,
1975+ std::string const& match_etag,
1976 unity::storage::provider::Context const& ctx) override;
1977
1978 boost::future<void> delete_item(std::string const& item_id,
1979@@ -50,10 +77,12 @@
1980 boost::future<unity::storage::provider::Item> move(
1981 std::string const& item_id, std::string const& new_parent_id,
1982 std::string const& new_name,
1983+ std::vector<std::string> const& metadata_keys,
1984 unity::storage::provider::Context const& ctx) override;
1985 boost::future<unity::storage::provider::Item> copy(
1986 std::string const& item_id, std::string const& new_parent_id,
1987 std::string const& new_name,
1988+ std::vector<std::string> const& metadata_keys,
1989 unity::storage::provider::Context const& ctx) override;
1990
1991 virtual QUrl base_url(
1992@@ -61,7 +90,13 @@
1993 virtual QNetworkReply *send_request(
1994 QNetworkRequest& request, QByteArray const& verb, QIODevice* data,
1995 unity::storage::provider::Context const& ctx) const = 0;
1996+ virtual unity::storage::provider::Item make_item(
1997+ QUrl const& href, QUrl const& base_url,
1998+ std::vector<MultiStatusProperty> const& properties) const;
1999
2000 protected:
2001 std::unique_ptr<QNetworkAccessManager> const network_;
2002+
2003+private:
2004+ inline std::shared_ptr<DavProvider> shared_from_this();
2005 };
2006
2007=== added file 'src/DavUploadJob.cpp'
2008--- src/DavUploadJob.cpp 1970-01-01 00:00:00 +0000
2009+++ src/DavUploadJob.cpp 2016-11-25 05:01:23 +0000
2010@@ -0,0 +1,140 @@
2011+/*
2012+ * Copyright (C) 2016 Canonical Ltd.
2013+ *
2014+ * This program is free software: you can redistribute it and/or modify
2015+ * it under the terms of the GNU General Public License version 3 as
2016+ * published by the Free Software Foundation.
2017+ *
2018+ * This program is distributed in the hope that it will be useful,
2019+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2020+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2021+ * GNU General Public License for more details.
2022+ *
2023+ * You should have received a copy of the GNU General Public License
2024+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2025+ *
2026+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2027+ */
2028+
2029+#include "DavUploadJob.h"
2030+#include "DavProvider.h"
2031+#include "RetrieveMetadataHandler.h"
2032+#include "item_id.h"
2033+#include "http_error.h"
2034+
2035+#include <unity/storage/provider/Exceptions.h>
2036+
2037+#include <unistd.h>
2038+#include <cassert>
2039+#include <cstdint>
2040+#include <string>
2041+
2042+using namespace std;
2043+using namespace unity::storage::provider;
2044+using unity::storage::ItemType;
2045+
2046+namespace
2047+{
2048+
2049+string make_upload_id()
2050+{
2051+ static int counter = 0;
2052+ return to_string(counter++);
2053+}
2054+
2055+}
2056+
2057+DavUploadJob::DavUploadJob(shared_ptr<DavProvider> const& provider,
2058+ string const& item_id, int64_t size,
2059+ string const& content_type, bool allow_overwrite,
2060+ string const& old_etag, Context const& ctx)
2061+ : QObject(), UploadJob(make_upload_id()), provider_(provider),
2062+ item_id_(item_id), base_url_(provider->base_url(ctx)), size_(size),
2063+ context_(ctx)
2064+{
2065+ QNetworkRequest request(id_to_url(item_id, base_url_));
2066+ if (!content_type.empty())
2067+ {
2068+ request.setHeader(QNetworkRequest::ContentTypeHeader,
2069+ QByteArray::fromStdString(content_type));
2070+ }
2071+ if (!allow_overwrite)
2072+ {
2073+ request.setRawHeader(QByteArrayLiteral("If-None-Match"),
2074+ QByteArrayLiteral("*"));
2075+ }
2076+ if (!old_etag.empty())
2077+ {
2078+ request.setRawHeader(QByteArrayLiteral("If-Match"),
2079+ QByteArray::fromStdString(old_etag));
2080+ }
2081+ request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant::fromValue(size));
2082+ request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true);
2083+
2084+ reader_.setSocketDescriptor(
2085+ dup(read_socket()), QLocalSocket::ConnectedState, QIODevice::ReadOnly);
2086+ reply_.reset(provider->send_request(
2087+ request, QByteArrayLiteral("PUT"), &reader_, ctx));
2088+ assert(reply_.get() != nullptr);
2089+ connect(reply_.get(), &QNetworkReply::finished,
2090+ this, &DavUploadJob::onReplyFinished);
2091+}
2092+
2093+DavUploadJob::~DavUploadJob() = default;
2094+
2095+void DavUploadJob::onReplyFinished()
2096+{
2097+ if (promise_set_)
2098+ {
2099+ return;
2100+ }
2101+ auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
2102+ // Is this a success status code?
2103+ if (status / 100 != 2)
2104+ {
2105+ promise_.set_exception(
2106+ translate_http_error(reply_.get(), QByteArray(), item_id_));
2107+ promise_set_ = true;
2108+ return;
2109+ }
2110+ // Queue up a PROPFIND request to retrieve the metadata for the upload.
2111+ metadata_.reset(
2112+ new RetrieveMetadataHandler(
2113+ provider_, item_id_, context_,
2114+ [this](Item const& item, boost::exception_ptr const& error) {
2115+ if (promise_set_)
2116+ {
2117+ return;
2118+ }
2119+ if (error)
2120+ {
2121+ promise_.set_exception(error);
2122+ }
2123+ else
2124+ {
2125+ promise_.set_value(item);
2126+ }
2127+ promise_set_ = true;
2128+ }));
2129+}
2130+
2131+boost::future<void> DavUploadJob::cancel()
2132+{
2133+ if (!promise_set_)
2134+ {
2135+ if (metadata_)
2136+ {
2137+ metadata_->abort();
2138+ }
2139+ else
2140+ {
2141+ reply_->abort();
2142+ }
2143+ }
2144+ return boost::make_ready_future();
2145+}
2146+
2147+boost::future<Item> DavUploadJob::finish()
2148+{
2149+ return promise_.get_future();
2150+}
2151
2152=== added file 'src/DavUploadJob.h'
2153--- src/DavUploadJob.h 1970-01-01 00:00:00 +0000
2154+++ src/DavUploadJob.h 2016-11-25 05:01:23 +0000
2155@@ -0,0 +1,65 @@
2156+/*
2157+ * Copyright (C) 2016 Canonical Ltd.
2158+ *
2159+ * This program is free software: you can redistribute it and/or modify
2160+ * it under the terms of the GNU General Public License version 3 as
2161+ * published by the Free Software Foundation.
2162+ *
2163+ * This program is distributed in the hope that it will be useful,
2164+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2165+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2166+ * GNU General Public License for more details.
2167+ *
2168+ * You should have received a copy of the GNU General Public License
2169+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2170+ *
2171+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2172+ */
2173+
2174+#pragma once
2175+
2176+#include <QLocalSocket>
2177+#include <QNetworkReply>
2178+#include <QObject>
2179+#include <QUrl>
2180+#include <unity/storage/provider/ProviderBase.h>
2181+#include <unity/storage/provider/UploadJob.h>
2182+
2183+#include <cstdint>
2184+#include <memory>
2185+#include <string>
2186+
2187+class DavProvider;
2188+class RetrieveMetadataHandler;
2189+
2190+class DavUploadJob : public QObject, public unity::storage::provider::UploadJob
2191+{
2192+ Q_OBJECT
2193+public:
2194+ DavUploadJob(std::shared_ptr<DavProvider> const& provider,
2195+ std::string const& item_id, int64_t size,
2196+ std::string const& content_type, bool allow_overwrite,
2197+ std::string const& old_etag,
2198+ unity::storage::provider::Context const& ctx);
2199+ ~DavUploadJob();
2200+
2201+ boost::future<void> cancel() override;
2202+ boost::future<unity::storage::provider::Item> finish() override;
2203+
2204+private Q_SLOTS:
2205+ void onReplyFinished();
2206+
2207+private:
2208+ std::shared_ptr<DavProvider> const provider_;
2209+ std::string const item_id_;
2210+ QUrl const base_url_;
2211+ int64_t const size_;
2212+ unity::storage::provider::Context const context_;
2213+ QLocalSocket reader_;
2214+ std::unique_ptr<QNetworkReply> reply_;
2215+
2216+ std::unique_ptr<RetrieveMetadataHandler> metadata_;
2217+
2218+ bool promise_set_ = false;
2219+ boost::promise<unity::storage::provider::Item> promise_;
2220+};
2221
2222=== added file 'src/DeleteHandler.cpp'
2223--- src/DeleteHandler.cpp 1970-01-01 00:00:00 +0000
2224+++ src/DeleteHandler.cpp 2016-11-25 05:01:23 +0000
2225@@ -0,0 +1,62 @@
2226+/*
2227+ * Copyright (C) 2016 Canonical Ltd.
2228+ *
2229+ * This program is free software: you can redistribute it and/or modify
2230+ * it under the terms of the GNU General Public License version 3 as
2231+ * published by the Free Software Foundation.
2232+ *
2233+ * This program is distributed in the hope that it will be useful,
2234+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2235+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2236+ * GNU General Public License for more details.
2237+ *
2238+ * You should have received a copy of the GNU General Public License
2239+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2240+ *
2241+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2242+ */
2243+
2244+#include "DeleteHandler.h"
2245+#include "DavProvider.h"
2246+#include "item_id.h"
2247+#include "http_error.h"
2248+
2249+using namespace std;
2250+using namespace unity::storage::provider;
2251+
2252+DeleteHandler::DeleteHandler(shared_ptr<DavProvider> const& provider,
2253+ string const& item_id,
2254+ Context const& ctx)
2255+ : provider_(provider), item_id_(item_id)
2256+{
2257+ QUrl const base_url = provider->base_url(ctx);
2258+ QNetworkRequest request(id_to_url(item_id_, base_url));
2259+ reply_.reset(provider->send_request(request, QByteArrayLiteral("DELETE"),
2260+ nullptr, ctx));
2261+ connect(reply_.get(), &QNetworkReply::finished,
2262+ this, &DeleteHandler::onFinished);
2263+}
2264+
2265+DeleteHandler::~DeleteHandler() = default;
2266+
2267+boost::future<void> DeleteHandler::get_future()
2268+{
2269+ return promise_.get_future();
2270+}
2271+
2272+void DeleteHandler::onFinished()
2273+{
2274+ auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
2275+
2276+ if (status / 100 == 2)
2277+ {
2278+ promise_.set_value();
2279+ }
2280+ else
2281+ {
2282+ promise_.set_exception(
2283+ translate_http_error(reply_.get(), QByteArray(), item_id_));
2284+ }
2285+
2286+ deleteLater();
2287+}
2288
2289=== added file 'src/DeleteHandler.h'
2290--- src/DeleteHandler.h 1970-01-01 00:00:00 +0000
2291+++ src/DeleteHandler.h 2016-11-25 05:01:23 +0000
2292@@ -0,0 +1,49 @@
2293+/*
2294+ * Copyright (C) 2016 Canonical Ltd.
2295+ *
2296+ * This program is free software: you can redistribute it and/or modify
2297+ * it under the terms of the GNU General Public License version 3 as
2298+ * published by the Free Software Foundation.
2299+ *
2300+ * This program is distributed in the hope that it will be useful,
2301+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2302+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2303+ * GNU General Public License for more details.
2304+ *
2305+ * You should have received a copy of the GNU General Public License
2306+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2307+ *
2308+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2309+ */
2310+
2311+#pragma once
2312+
2313+#include <QBuffer>
2314+#include <QObject>
2315+#include <QNetworkReply>
2316+#include <unity/storage/provider/ProviderBase.h>
2317+
2318+#include <memory>
2319+
2320+class DavProvider;
2321+
2322+class DeleteHandler : public QObject {
2323+ Q_OBJECT
2324+public:
2325+ DeleteHandler(std::shared_ptr<DavProvider> const& provider, std::string const& item_id,
2326+ unity::storage::provider::Context const& ctx);
2327+ ~DeleteHandler();
2328+
2329+ boost::future<void> get_future();
2330+
2331+private Q_SLOTS:
2332+ void onFinished();
2333+
2334+private:
2335+ boost::promise<void> promise_;
2336+
2337+ std::shared_ptr<DavProvider> const provider_;
2338+ std::string const item_id_;
2339+
2340+ std::unique_ptr<QNetworkReply> reply_;
2341+};
2342
2343=== added file 'src/ListHandler.cpp'
2344--- src/ListHandler.cpp 1970-01-01 00:00:00 +0000
2345+++ src/ListHandler.cpp 2016-11-25 05:01:23 +0000
2346@@ -0,0 +1,59 @@
2347+/*
2348+ * Copyright (C) 2016 Canonical Ltd.
2349+ *
2350+ * This program is free software: you can redistribute it and/or modify
2351+ * it under the terms of the GNU General Public License version 3 as
2352+ * published by the Free Software Foundation.
2353+ *
2354+ * This program is distributed in the hope that it will be useful,
2355+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2356+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2357+ * GNU General Public License for more details.
2358+ *
2359+ * You should have received a copy of the GNU General Public License
2360+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2361+ *
2362+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2363+ */
2364+
2365+#include "ListHandler.h"
2366+
2367+#include <unity/storage/provider/Exceptions.h>
2368+
2369+#include <algorithm>
2370+#include <cassert>
2371+
2372+using namespace std;
2373+using namespace unity::storage::provider;
2374+
2375+ListHandler::ListHandler(std::shared_ptr<DavProvider> const& provider,
2376+ string const& parent_id, Context const& ctx)
2377+ : PropFindHandler(provider, parent_id, 1, ctx)
2378+{
2379+}
2380+
2381+ListHandler::~ListHandler() = default;
2382+
2383+boost::future<tuple<ItemList,string>> ListHandler::get_future()
2384+{
2385+ return promise_.get_future();
2386+}
2387+
2388+void ListHandler::finish()
2389+{
2390+ deleteLater();
2391+
2392+ if (error_)
2393+ {
2394+ promise_.set_exception(error_);
2395+ return;
2396+ }
2397+ // A "Depth: 1" PROPFIND will also return data for the parent URL
2398+ // itself, so remove it from the list.
2399+ items_.erase(remove_if(items_.begin(), items_.end(),
2400+ [&](Item const& item) -> bool {
2401+ return item.item_id == item_id_;
2402+ }), items_.end());
2403+
2404+ promise_.set_value(make_tuple(move(items_), string()));
2405+}
2406
2407=== added file 'src/ListHandler.h'
2408--- src/ListHandler.h 1970-01-01 00:00:00 +0000
2409+++ src/ListHandler.h 2016-11-25 05:01:23 +0000
2410@@ -0,0 +1,43 @@
2411+/*
2412+ * Copyright (C) 2016 Canonical Ltd.
2413+ *
2414+ * This program is free software: you can redistribute it and/or modify
2415+ * it under the terms of the GNU General Public License version 3 as
2416+ * published by the Free Software Foundation.
2417+ *
2418+ * This program is distributed in the hope that it will be useful,
2419+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2420+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2421+ * GNU General Public License for more details.
2422+ *
2423+ * You should have received a copy of the GNU General Public License
2424+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2425+ *
2426+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2427+ */
2428+
2429+#pragma once
2430+
2431+#include <QObject>
2432+
2433+#include <memory>
2434+#include <tuple>
2435+
2436+#include "PropFindHandler.h"
2437+
2438+class ListHandler : public PropFindHandler {
2439+ Q_OBJECT
2440+public:
2441+ ListHandler(std::shared_ptr<DavProvider> const& provider,
2442+ std::string const& parent_id,
2443+ unity::storage::provider::Context const& ctx);
2444+ ~ListHandler();
2445+
2446+ boost::future<std::tuple<unity::storage::provider::ItemList,std::string>> get_future();
2447+
2448+private:
2449+ boost::promise<std::tuple<unity::storage::provider::ItemList,std::string>> promise_;
2450+
2451+protected:
2452+ void finish() override;
2453+};
2454
2455=== added file 'src/LookupHandler.cpp'
2456--- src/LookupHandler.cpp 1970-01-01 00:00:00 +0000
2457+++ src/LookupHandler.cpp 2016-11-25 05:01:23 +0000
2458@@ -0,0 +1,54 @@
2459+/*
2460+ * Copyright (C) 2016 Canonical Ltd.
2461+ *
2462+ * This program is free software: you can redistribute it and/or modify
2463+ * it under the terms of the GNU General Public License version 3 as
2464+ * published by the Free Software Foundation.
2465+ *
2466+ * This program is distributed in the hope that it will be useful,
2467+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2468+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2469+ * GNU General Public License for more details.
2470+ *
2471+ * You should have received a copy of the GNU General Public License
2472+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2473+ *
2474+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2475+ */
2476+
2477+#include "LookupHandler.h"
2478+
2479+#include <cassert>
2480+
2481+using namespace std;
2482+using namespace unity::storage::provider;
2483+
2484+LookupHandler::LookupHandler(std::shared_ptr<DavProvider> const& provider,
2485+ string const& item_id, Context const& ctx)
2486+ : PropFindHandler(provider, item_id, 0, ctx)
2487+{
2488+}
2489+
2490+LookupHandler::~LookupHandler() = default;
2491+
2492+boost::future<ItemList> LookupHandler::get_future()
2493+{
2494+ return promise_.get_future();
2495+}
2496+
2497+void LookupHandler::finish()
2498+{
2499+ if (error_)
2500+ {
2501+ promise_.set_exception(error_);
2502+ return;
2503+ }
2504+ // Perform some sanity checks on the result
2505+ if (items_.size() != 1)
2506+ {
2507+ promise_.set_exception(RemoteCommsException("Unexpectedly received " + to_string(items_.size()) + " items from PROPFIND request"));
2508+ return;
2509+ }
2510+
2511+ promise_.set_value(move(items_));
2512+}
2513
2514=== added file 'src/LookupHandler.h'
2515--- src/LookupHandler.h 1970-01-01 00:00:00 +0000
2516+++ src/LookupHandler.h 2016-11-25 05:01:23 +0000
2517@@ -0,0 +1,42 @@
2518+/*
2519+ * Copyright (C) 2016 Canonical Ltd.
2520+ *
2521+ * This program is free software: you can redistribute it and/or modify
2522+ * it under the terms of the GNU General Public License version 3 as
2523+ * published by the Free Software Foundation.
2524+ *
2525+ * This program is distributed in the hope that it will be useful,
2526+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2527+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2528+ * GNU General Public License for more details.
2529+ *
2530+ * You should have received a copy of the GNU General Public License
2531+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2532+ *
2533+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2534+ */
2535+
2536+#pragma once
2537+
2538+#include <QObject>
2539+
2540+#include <memory>
2541+
2542+#include "PropFindHandler.h"
2543+
2544+class LookupHandler : public PropFindHandler {
2545+ Q_OBJECT
2546+public:
2547+ LookupHandler(std::shared_ptr<DavProvider> const& provider,
2548+ std::string const& item_id,
2549+ unity::storage::provider::Context const& ctx);
2550+ ~LookupHandler();
2551+
2552+ boost::future<unity::storage::provider::ItemList> get_future();
2553+
2554+private:
2555+ boost::promise<unity::storage::provider::ItemList> promise_;
2556+
2557+protected:
2558+ void finish() override;
2559+};
2560
2561=== added file 'src/MetadataHandler.cpp'
2562--- src/MetadataHandler.cpp 1970-01-01 00:00:00 +0000
2563+++ src/MetadataHandler.cpp 2016-11-25 05:01:23 +0000
2564@@ -0,0 +1,61 @@
2565+/*
2566+ * Copyright (C) 2016 Canonical Ltd.
2567+ *
2568+ * This program is free software: you can redistribute it and/or modify
2569+ * it under the terms of the GNU General Public License version 3 as
2570+ * published by the Free Software Foundation.
2571+ *
2572+ * This program is distributed in the hope that it will be useful,
2573+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2574+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2575+ * GNU General Public License for more details.
2576+ *
2577+ * You should have received a copy of the GNU General Public License
2578+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2579+ *
2580+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2581+ */
2582+
2583+#include "MetadataHandler.h"
2584+
2585+#include <cassert>
2586+
2587+using namespace std;
2588+using namespace unity::storage::provider;
2589+
2590+MetadataHandler::MetadataHandler(shared_ptr<DavProvider> const& provider,
2591+ string const& item_id, Context const& ctx)
2592+ : PropFindHandler(provider, item_id, 0, ctx)
2593+{
2594+}
2595+
2596+MetadataHandler::~MetadataHandler() = default;
2597+
2598+boost::future<Item> MetadataHandler::get_future()
2599+{
2600+ return promise_.get_future();
2601+}
2602+
2603+void MetadataHandler::finish()
2604+{
2605+ deleteLater();
2606+
2607+ if (error_)
2608+ {
2609+ promise_.set_exception(error_);
2610+ return;
2611+ }
2612+ // Perform some sanity checks on the result
2613+ if (items_.size() != 1)
2614+ {
2615+ promise_.set_exception(RemoteCommsException("Unexpectedly received " + to_string(items_.size()) + " items from PROPFIND request"));
2616+ return;
2617+ }
2618+ if (items_[0].item_id != item_id_)
2619+ {
2620+ promise_.set_exception(RemoteCommsException("PROPFIND request returned data about the wrong item"));
2621+ return;
2622+ }
2623+
2624+ promise_.set_value(move(items_[0]));
2625+}
2626
2627=== added file 'src/MetadataHandler.h'
2628--- src/MetadataHandler.h 1970-01-01 00:00:00 +0000
2629+++ src/MetadataHandler.h 2016-11-25 05:01:23 +0000
2630@@ -0,0 +1,42 @@
2631+/*
2632+ * Copyright (C) 2016 Canonical Ltd.
2633+ *
2634+ * This program is free software: you can redistribute it and/or modify
2635+ * it under the terms of the GNU General Public License version 3 as
2636+ * published by the Free Software Foundation.
2637+ *
2638+ * This program is distributed in the hope that it will be useful,
2639+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2640+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2641+ * GNU General Public License for more details.
2642+ *
2643+ * You should have received a copy of the GNU General Public License
2644+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2645+ *
2646+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2647+ */
2648+
2649+#pragma once
2650+
2651+#include <QObject>
2652+
2653+#include <memory>
2654+
2655+#include "PropFindHandler.h"
2656+
2657+class MetadataHandler : public PropFindHandler {
2658+ Q_OBJECT
2659+public:
2660+ MetadataHandler(std::shared_ptr<DavProvider> const& provider,
2661+ std::string const& item_id,
2662+ unity::storage::provider::Context const& ctx);
2663+ ~MetadataHandler();
2664+
2665+ boost::future<unity::storage::provider::Item> get_future();
2666+
2667+private:
2668+ boost::promise<unity::storage::provider::Item> promise_;
2669+
2670+protected:
2671+ void finish() override;
2672+};
2673
2674=== modified file 'src/MultiStatusParser.cpp'
2675--- src/MultiStatusParser.cpp 2016-09-20 01:50:46 +0000
2676+++ src/MultiStatusParser.cpp 2016-11-25 05:01:23 +0000
2677@@ -1,3 +1,21 @@
2678+/*
2679+ * Copyright (C) 2016 Canonical Ltd.
2680+ *
2681+ * This program is free software: you can redistribute it and/or modify
2682+ * it under the terms of the GNU General Public License version 3 as
2683+ * published by the Free Software Foundation.
2684+ *
2685+ * This program is distributed in the hope that it will be useful,
2686+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2687+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2688+ * GNU General Public License for more details.
2689+ *
2690+ * You should have received a copy of the GNU General Public License
2691+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2692+ *
2693+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2694+ */
2695+
2696 #include "MultiStatusParser.h"
2697
2698 #include <QDebug>
2699@@ -127,10 +145,16 @@
2700 {
2701 return;
2702 }
2703- // Perform one final call to parseContinue() to signal end of file
2704 if (started_)
2705 {
2706- reader_.parseContinue();
2707+ // Drain the remaining data from the input channel
2708+ while (reader_.parseContinue())
2709+ {
2710+ if (handler_->atEnd())
2711+ {
2712+ break;
2713+ }
2714+ }
2715 }
2716 if (error_string_.isEmpty() && !handler_->atEnd())
2717 {
2718@@ -146,7 +170,6 @@
2719 return true;
2720 }
2721
2722-
2723 bool MultiStatusParser::Handler::startElement(QString const& namespace_uri,
2724 QString const& local_name,
2725 QString const& qname,
2726@@ -297,6 +320,7 @@
2727 break;
2728 case ParseState::multistatus:
2729 state_ = ParseState::start;
2730+ at_end_ = true;
2731 break;
2732 case ParseState::response:
2733 Q_EMIT parser_->response(current_href_, current_properties_,
2734@@ -318,7 +342,7 @@
2735 for (auto& prop : current_propstat_)
2736 {
2737 prop.status = current_propstat_status_;
2738- current_properties_.emplace_back(std::move(prop));
2739+ current_properties_.emplace_back(move(prop));
2740 }
2741 current_propstat_.clear();
2742 state_ = ParseState::response;
2743
2744=== modified file 'src/MultiStatusParser.h'
2745--- src/MultiStatusParser.h 2016-09-20 01:50:46 +0000
2746+++ src/MultiStatusParser.h 2016-11-25 05:01:23 +0000
2747@@ -1,3 +1,21 @@
2748+/*
2749+ * Copyright (C) 2016 Canonical Ltd.
2750+ *
2751+ * This program is free software: you can redistribute it and/or modify
2752+ * it under the terms of the GNU General Public License version 3 as
2753+ * published by the Free Software Foundation.
2754+ *
2755+ * This program is distributed in the hope that it will be useful,
2756+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2757+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2758+ * GNU General Public License for more details.
2759+ *
2760+ * You should have received a copy of the GNU General Public License
2761+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2762+ *
2763+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2764+ */
2765+
2766 #pragma once
2767
2768 #include <QIODevice>
2769
2770=== modified file 'src/OwncloudProvider.cpp'
2771--- src/OwncloudProvider.cpp 2016-09-22 06:40:07 +0000
2772+++ src/OwncloudProvider.cpp 2016-11-25 05:01:23 +0000
2773@@ -1,3 +1,21 @@
2774+/*
2775+ * Copyright (C) 2016 Canonical Ltd.
2776+ *
2777+ * This program is free software: you can redistribute it and/or modify
2778+ * it under the terms of the GNU General Public License version 3 as
2779+ * published by the Free Software Foundation.
2780+ *
2781+ * This program is distributed in the hope that it will be useful,
2782+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2783+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2784+ * GNU General Public License for more details.
2785+ *
2786+ * You should have received a copy of the GNU General Public License
2787+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2788+ *
2789+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2790+ */
2791+
2792 #include "OwncloudProvider.h"
2793
2794 #include <QNetworkAccessManager>
2795@@ -15,8 +33,8 @@
2796
2797 QUrl OwncloudProvider::base_url(Context const& ctx) const
2798 {
2799- Q_UNUSED(ctx);
2800- return QUrl("http://localhost:8080/");
2801+ const auto& creds = boost::get<PasswordCredentials>(ctx.credentials);
2802+ return QUrl(QStringLiteral("http://localhost:8888/owncloud/remote.php/dav/files/%1/").arg(QString::fromStdString(creds.username)));
2803 }
2804
2805 QNetworkReply *OwncloudProvider::send_request(
2806@@ -25,9 +43,13 @@
2807 {
2808 Q_UNUSED(ctx);
2809
2810- const auto credentials = QByteArrayLiteral("user:pass");
2811+ const auto& creds = boost::get<PasswordCredentials>(ctx.credentials);
2812+ const auto credentials = QByteArray::fromStdString(creds.username + ":" +
2813+ creds.password);
2814 request.setRawHeader(QByteArrayLiteral("Authorization"),
2815 QByteArrayLiteral("Basic ") + credentials.toBase64());
2816+ printf("Sending request to %s with credentials %s\n",
2817+ request.url().toEncoded().constData(), credentials.constData());
2818 QNetworkReply *reply = network_->sendCustomRequest(request, verb, data);
2819 return reply;
2820 }
2821
2822=== modified file 'src/OwncloudProvider.h'
2823--- src/OwncloudProvider.h 2016-09-22 03:36:22 +0000
2824+++ src/OwncloudProvider.h 2016-11-25 05:01:23 +0000
2825@@ -1,3 +1,21 @@
2826+/*
2827+ * Copyright (C) 2016 Canonical Ltd.
2828+ *
2829+ * This program is free software: you can redistribute it and/or modify
2830+ * it under the terms of the GNU General Public License version 3 as
2831+ * published by the Free Software Foundation.
2832+ *
2833+ * This program is distributed in the hope that it will be useful,
2834+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2835+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2836+ * GNU General Public License for more details.
2837+ *
2838+ * You should have received a copy of the GNU General Public License
2839+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2840+ *
2841+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2842+ */
2843+
2844 #pragma once
2845
2846 #include "DavProvider.h"
2847
2848=== renamed file 'src/RootsHandler.cpp' => 'src/PropFindHandler.cpp'
2849--- src/RootsHandler.cpp 2016-09-27 06:28:45 +0000
2850+++ src/PropFindHandler.cpp 2016-11-25 05:01:23 +0000
2851@@ -1,15 +1,29 @@
2852-#include "RootsHandler.h"
2853+/*
2854+ * Copyright (C) 2016 Canonical Ltd.
2855+ *
2856+ * This program is free software: you can redistribute it and/or modify
2857+ * it under the terms of the GNU General Public License version 3 as
2858+ * published by the Free Software Foundation.
2859+ *
2860+ * This program is distributed in the hope that it will be useful,
2861+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2862+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2863+ * GNU General Public License for more details.
2864+ *
2865+ * You should have received a copy of the GNU General Public License
2866+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2867+ *
2868+ * Authored by: James Henstridge <james.henstridge@canonical.com>
2869+ */
2870+
2871+#include "PropFindHandler.h"
2872 #include "item_id.h"
2873-
2874-#include <QDateTime>
2875-#include <QDebug>
2876-#include <unity/storage/provider/metadata_keys.h>
2877+#include "http_error.h"
2878
2879 #include <cassert>
2880
2881 using namespace std;
2882 using namespace unity::storage::provider;
2883-using unity::storage::ItemType;
2884
2885 namespace
2886 {
2887@@ -26,161 +40,143 @@
2888 </D:propfind>)");
2889 }
2890
2891-RootsHandler::RootsHandler(DavProvider const& provider, Context const& ctx)
2892- : provider_(provider), base_url_(provider.base_url(ctx))
2893+PropFindHandler::PropFindHandler(shared_ptr<DavProvider> const& provider,
2894+ string const& item_id, int depth,
2895+ Context const& ctx)
2896+ : provider_(provider), base_url_(provider->base_url(ctx)), item_id_(item_id)
2897 {
2898- QNetworkRequest request(base_url_);
2899- request.setRawHeader(QByteArrayLiteral("Depth"), QByteArrayLiteral("0"));
2900+ QNetworkRequest request(id_to_url(item_id_, base_url_));
2901+ request.setRawHeader(QByteArrayLiteral("Depth"), QByteArray::number(depth));
2902 request.setHeader(QNetworkRequest::ContentTypeHeader,
2903 QStringLiteral("application/xml; charset=\"utf-8\""));
2904 request.setHeader(QNetworkRequest::ContentLengthHeader, PROPFIND_BODY.size());
2905 request_body_.setData(PROPFIND_BODY);
2906 request_body_.open(QIODevice::ReadOnly);
2907
2908- reply_.reset(provider.send_request(request, QByteArrayLiteral("PROPFIND"),
2909- &request_body_, ctx));
2910+ reply_.reset(provider->send_request(request, QByteArrayLiteral("PROPFIND"),
2911+ &request_body_, ctx));
2912 assert(reply_.get() != nullptr);
2913
2914- connect(reply_.get(), static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
2915- this, &RootsHandler::onError);
2916 connect(reply_.get(), &QIODevice::readyRead,
2917- this, &RootsHandler::onReadyRead);
2918-}
2919-
2920-RootsHandler::~RootsHandler()
2921-{
2922-}
2923-
2924-boost::future<unity::storage::provider::ItemList> RootsHandler::get_future()
2925-{
2926- return promise_.get_future();
2927-}
2928-
2929-void RootsHandler::sendError(StorageException const& error)
2930-{
2931- if (promise_set_) {
2932- return;
2933- }
2934-
2935- promise_.set_exception(error);
2936- promise_set_ = true;
2937-
2938- deleteLater();
2939-}
2940-
2941-void RootsHandler::sendResponse()
2942-{
2943- if (promise_set_) {
2944- return;
2945- }
2946-
2947- promise_.set_value(roots_);
2948- promise_set_ = true;
2949-
2950- deleteLater();
2951-}
2952-
2953-void RootsHandler::onError(QNetworkReply::NetworkError code)
2954-{
2955- sendError(RemoteCommsException("Error from QNetworkReply: " +
2956- to_string(code)));
2957-}
2958-
2959-void RootsHandler::onReadyRead()
2960-{
2961- disconnect(reply_.get(), &QIODevice::readyRead,
2962- this, &RootsHandler::onReadyRead);
2963- auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
2964-
2965- if (status != 207)
2966- {
2967- sendError(RemoteCommsException("Expected 207 response, but got " + to_string(status)));
2968- return;
2969- }
2970- parser_.reset(new MultiStatusParser(reply_->request().url(), reply_.get()));
2971- connect(parser_.get(), &MultiStatusParser::response,
2972- this, &RootsHandler::onParserResponse);
2973- connect(parser_.get(), &MultiStatusParser::finished,
2974- this, &RootsHandler::onParserFinished);
2975-}
2976-
2977-void RootsHandler::onParserResponse(QUrl const& href, std::vector<MultiStatusProperty> const& properties, int status)
2978-{
2979- if (href != base_url_)
2980- {
2981- sendError(RemoteCommsException("PROPFIND returned information about a different URL"));
2982- return;
2983- }
2984+ this, &PropFindHandler::onReplyReadyRead);
2985+ connect(reply_.get(), &QNetworkReply::finished,
2986+ this, &PropFindHandler::onReplyFinished);
2987+}
2988+
2989+PropFindHandler::~PropFindHandler()
2990+{
2991+}
2992+
2993+void PropFindHandler::abort()
2994+{
2995+ if (finished_)
2996+ {
2997+ return;
2998+ }
2999+ finished_ = true;
3000+ reply_->abort();
3001+}
3002+
3003+void PropFindHandler::reportError(StorageException const& error)
3004+{
3005+ reportError(boost::copy_exception(error));
3006+}
3007+
3008+void PropFindHandler::reportError(boost::exception_ptr const& ep)
3009+{
3010+ if (finished_)
3011+ {
3012+ return;
3013+ }
3014+
3015+ error_ = ep;
3016+ finished_ = true;
3017+ finish();
3018+}
3019+
3020+void PropFindHandler::reportSuccess()
3021+{
3022+ if (finished_)
3023+ {
3024+ return;
3025+ }
3026+
3027+ finished_ = true;
3028+ finish();
3029+}
3030+
3031+void PropFindHandler::onReplyReadyRead()
3032+{
3033+ if (!seen_headers_)
3034+ {
3035+ seen_headers_ = true;
3036+ auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
3037+ if (status == 207)
3038+ {
3039+ disconnect(reply_.get(), &QIODevice::readyRead,
3040+ this, &PropFindHandler::onReplyReadyRead);
3041+ parser_.reset(new MultiStatusParser(reply_->request().url(), reply_.get()));
3042+ connect(parser_.get(), &MultiStatusParser::response,
3043+ this, &PropFindHandler::onParserResponse);
3044+ connect(parser_.get(), &MultiStatusParser::finished,
3045+ this, &PropFindHandler::onParserFinished);
3046+ }
3047+ else
3048+ {
3049+ is_error_ = true;
3050+ }
3051+ }
3052+ if (is_error_)
3053+ {
3054+ if (error_body_.size() < MAX_ERROR_BODY_LENGTH)
3055+ {
3056+ error_body_.append(reply_->readAll());
3057+ }
3058+ else
3059+ {
3060+ reply_->close();
3061+ }
3062+ }
3063+}
3064+
3065+void PropFindHandler::onReplyFinished()
3066+{
3067+ if (!seen_headers_ || is_error_)
3068+ {
3069+ reportError(translate_http_error(reply_.get(), error_body_, item_id_));
3070+ }
3071+}
3072+
3073+void PropFindHandler::onParserResponse(QUrl const& href, vector<MultiStatusProperty> const& properties, int status)
3074+{
3075 if (status != 0 && status != 200)
3076 {
3077- sendError(RemoteCommsException("Failed to look up properties for root: " + to_string(status)));
3078+ reportError(RemoteCommsException("PROPFIND for " + href.toEncoded().toStdString() + " gave status " + to_string(status)));
3079 return;
3080 }
3081 try
3082 {
3083- Item item;
3084- item.item_id = url_to_id(href, base_url_);
3085- item.name = "Root";
3086- item.type = ItemType::root;
3087-
3088- for (const auto& prop : properties)
3089- {
3090- if (prop.status != 200) {
3091- // Don't warn about "404 Not Found" properties
3092- if (prop.status != 404) {
3093- qWarning() << "Got status" << prop.status << "for property"
3094- << prop.ns << prop.name;
3095- }
3096- continue;
3097- }
3098- if (prop.ns == "DAV:")
3099- {
3100- if (prop.name == "getetag")
3101- {
3102- item.etag = prop.value.toStdString();
3103- }
3104- else if (prop.name == "getcontentlength")
3105- {
3106- item.metadata[SIZE_IN_BYTES] = static_cast<int64_t>(prop.value.toLongLong());
3107- }
3108- else if (prop.name == "creationdate")
3109- {
3110- auto date = QDateTime::fromString(prop.value, Qt::RFC2822Date);
3111- if (date.isValid())
3112- {
3113- item.metadata[CREATION_TIME] = date.toString(Qt::ISODate).toStdString();
3114- }
3115- }
3116- else if (prop.name == "getlastmodified")
3117- {
3118- auto date = QDateTime::fromString(prop.value, Qt::RFC2822Date);
3119- if (date.isValid())
3120- {
3121- item.metadata[LAST_MODIFIED_TIME] = date.toString(Qt::ISODate).toStdString();
3122- }
3123- }
3124- }
3125- }
3126- roots_.emplace_back(std::move(item));
3127+ Item item = provider_->make_item(href, base_url_, properties);
3128+ items_.emplace_back(move(item));
3129 }
3130 catch (StorageException const& error)
3131 {
3132- sendError(error);
3133+ reportError(error);
3134 }
3135 catch (exception const& error)
3136 {
3137- sendError(RemoteCommsException(string("Error creating item: ") + error.what()));
3138+ reportError(RemoteCommsException(string("Error creating item: ") + error.what()));
3139 }
3140 }
3141
3142-void RootsHandler::onParserFinished()
3143+void PropFindHandler::onParserFinished()
3144 {
3145 if (parser_->errorString().isEmpty())
3146 {
3147- sendResponse();
3148+ reportSuccess();
3149 }
3150 else
3151 {
3152- sendError(RemoteCommsException("Error parsing Multi-Status response: " + parser_->errorString().toStdString()));
3153+ reportError(RemoteCommsException("Error parsing Multi-Status response: " + parser_->errorString().toStdString()));
3154 }
3155 }
3156
3157=== renamed file 'src/RootsHandler.h' => 'src/PropFindHandler.h'
3158--- src/RootsHandler.h 2016-09-22 03:36:22 +0000
3159+++ src/PropFindHandler.h 2016-11-25 05:01:23 +0000
3160@@ -1,3 +1,21 @@
3161+/*
3162+ * Copyright (C) 2016 Canonical Ltd.
3163+ *
3164+ * This program is free software: you can redistribute it and/or modify
3165+ * it under the terms of the GNU General Public License version 3 as
3166+ * published by the Free Software Foundation.
3167+ *
3168+ * This program is distributed in the hope that it will be useful,
3169+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3170+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3171+ * GNU General Public License for more details.
3172+ *
3173+ * You should have received a copy of the GNU General Public License
3174+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3175+ *
3176+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3177+ */
3178+
3179 #pragma once
3180
3181 #include <QBuffer>
3182@@ -10,37 +28,46 @@
3183 #include "DavProvider.h"
3184 #include "MultiStatusParser.h"
3185
3186-class RootsHandler : public QObject {
3187+class PropFindHandler : public QObject {
3188 Q_OBJECT
3189 public:
3190- RootsHandler(DavProvider const& provider,
3191- unity::storage::provider::Context const& ctx);
3192- ~RootsHandler();
3193-
3194- boost::future<unity::storage::provider::ItemList> get_future();
3195-
3196-private:
3197- void sendError(unity::storage::provider::StorageException const& error);
3198- void sendResponse();
3199+ PropFindHandler(std::shared_ptr<DavProvider> const& provider,
3200+ std::string const& item_id, int depth,
3201+ unity::storage::provider::Context const& ctx);
3202+ ~PropFindHandler();
3203+
3204+ void abort();
3205
3206 private Q_SLOTS:
3207 // From QNetworkReply
3208- void onError(QNetworkReply::NetworkError code);
3209- void onReadyRead();
3210+ void onReplyReadyRead();
3211+ void onReplyFinished();
3212
3213 // From MultiStatusParser
3214 void onParserResponse(QUrl const& href, std::vector<MultiStatusProperty> const& properties, int status);
3215 void onParserFinished();
3216
3217 private:
3218- bool promise_set_ = false;
3219+ void reportError(unity::storage::provider::StorageException const& error);
3220+ void reportError(boost::exception_ptr const& ep);
3221+ void reportSuccess();
3222+
3223+ bool seen_headers_ = false;
3224+ bool is_error_ = false;
3225+ bool finished_ = false;
3226 boost::promise<unity::storage::provider::ItemList> promise_;
3227
3228- DavProvider const& provider_;
3229+ std::shared_ptr<DavProvider> const provider_;
3230 QUrl base_url_;
3231 QBuffer request_body_;
3232 std::unique_ptr<QNetworkReply> reply_;
3233-
3234 std::unique_ptr<MultiStatusParser> parser_;
3235- unity::storage::provider::ItemList roots_;
3236+ QByteArray error_body_;
3237+
3238+protected:
3239+ virtual void finish() = 0;
3240+
3241+ std::string const item_id_;
3242+ unity::storage::provider::ItemList items_;
3243+ boost::exception_ptr error_;
3244 };
3245
3246=== added file 'src/RetrieveMetadataHandler.cpp'
3247--- src/RetrieveMetadataHandler.cpp 1970-01-01 00:00:00 +0000
3248+++ src/RetrieveMetadataHandler.cpp 2016-11-25 05:01:23 +0000
3249@@ -0,0 +1,52 @@
3250+/*
3251+ * Copyright (C) 2016 Canonical Ltd.
3252+ *
3253+ * This program is free software: you can redistribute it and/or modify
3254+ * it under the terms of the GNU General Public License version 3 as
3255+ * published by the Free Software Foundation.
3256+ *
3257+ * This program is distributed in the hope that it will be useful,
3258+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3259+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3260+ * GNU General Public License for more details.
3261+ *
3262+ * You should have received a copy of the GNU General Public License
3263+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3264+ *
3265+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3266+ */
3267+
3268+#include "RetrieveMetadataHandler.h"
3269+
3270+#include <unity/storage/provider/Exceptions.h>
3271+
3272+#include <cassert>
3273+
3274+using namespace std;
3275+using namespace unity::storage::provider;
3276+
3277+RetrieveMetadataHandler::RetrieveMetadataHandler(shared_ptr<DavProvider> const& provider,
3278+ string const& item_id,
3279+ Context const& ctx,
3280+ Callback callback)
3281+ : PropFindHandler(provider, item_id, 0, ctx), callback_(callback)
3282+{
3283+}
3284+
3285+RetrieveMetadataHandler::~RetrieveMetadataHandler() = default;
3286+
3287+void RetrieveMetadataHandler::finish()
3288+{
3289+ Item item;
3290+ boost::exception_ptr ex = error_;
3291+
3292+ if (items_.size() != 1)
3293+ {
3294+ ex = boost::copy_exception(RemoteCommsException("Unexpectedly received " + to_string(items_.size()) + " items from PROPFIND request"));
3295+ }
3296+ else
3297+ {
3298+ item = items_[0];
3299+ }
3300+ callback_(item, ex);
3301+}
3302
3303=== added file 'src/RetrieveMetadataHandler.h'
3304--- src/RetrieveMetadataHandler.h 1970-01-01 00:00:00 +0000
3305+++ src/RetrieveMetadataHandler.h 2016-11-25 05:01:23 +0000
3306@@ -0,0 +1,45 @@
3307+/*
3308+ * Copyright (C) 2016 Canonical Ltd.
3309+ *
3310+ * This program is free software: you can redistribute it and/or modify
3311+ * it under the terms of the GNU General Public License version 3 as
3312+ * published by the Free Software Foundation.
3313+ *
3314+ * This program is distributed in the hope that it will be useful,
3315+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3316+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3317+ * GNU General Public License for more details.
3318+ *
3319+ * You should have received a copy of the GNU General Public License
3320+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3321+ *
3322+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3323+ */
3324+
3325+#pragma once
3326+
3327+#include <QObject>
3328+
3329+#include <functional>
3330+#include <memory>
3331+
3332+#include "PropFindHandler.h"
3333+
3334+class RetrieveMetadataHandler : public PropFindHandler {
3335+ Q_OBJECT
3336+public:
3337+ typedef std::function<void(unity::storage::provider::Item const& item,
3338+ boost::exception_ptr const& error)> Callback;
3339+
3340+ RetrieveMetadataHandler(std::shared_ptr<DavProvider> const& provider,
3341+ std::string const& item_id,
3342+ unity::storage::provider::Context const& ctx,
3343+ Callback callback);
3344+ ~RetrieveMetadataHandler();
3345+
3346+private:
3347+ Callback const callback_;
3348+
3349+protected:
3350+ void finish() override;
3351+};
3352
3353=== added file 'src/RootsHandler.cpp'
3354--- src/RootsHandler.cpp 1970-01-01 00:00:00 +0000
3355+++ src/RootsHandler.cpp 2016-11-25 05:01:23 +0000
3356@@ -0,0 +1,61 @@
3357+/*
3358+ * Copyright (C) 2016 Canonical Ltd.
3359+ *
3360+ * This program is free software: you can redistribute it and/or modify
3361+ * it under the terms of the GNU General Public License version 3 as
3362+ * published by the Free Software Foundation.
3363+ *
3364+ * This program is distributed in the hope that it will be useful,
3365+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3366+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3367+ * GNU General Public License for more details.
3368+ *
3369+ * You should have received a copy of the GNU General Public License
3370+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3371+ *
3372+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3373+ */
3374+
3375+#include "RootsHandler.h"
3376+
3377+#include <cassert>
3378+
3379+using namespace std;
3380+using namespace unity::storage::provider;
3381+
3382+RootsHandler::RootsHandler(shared_ptr<DavProvider> const& provider,
3383+ Context const& ctx)
3384+ : PropFindHandler(provider, ".", 0, ctx)
3385+{
3386+}
3387+
3388+RootsHandler::~RootsHandler() = default;
3389+
3390+boost::future<ItemList> RootsHandler::get_future()
3391+{
3392+ return promise_.get_future();
3393+}
3394+
3395+void RootsHandler::finish()
3396+{
3397+ deleteLater();
3398+
3399+ if (error_)
3400+ {
3401+ promise_.set_exception(error_);
3402+ return;
3403+ }
3404+ // Perform some sanity checks on the result
3405+ if (items_.size() != 1)
3406+ {
3407+ promise_.set_exception(RemoteCommsException("Unexpectedly received " + to_string(items_.size()) + " items from PROPFIND request"));
3408+ return;
3409+ }
3410+ if (items_[0].item_id != ".")
3411+ {
3412+ promise_.set_exception(RemoteCommsException("PROPFIND request returned data about the wrong item"));
3413+ return;
3414+ }
3415+
3416+ promise_.set_value(move(items_));
3417+}
3418
3419=== added file 'src/RootsHandler.h'
3420--- src/RootsHandler.h 1970-01-01 00:00:00 +0000
3421+++ src/RootsHandler.h 2016-11-25 05:01:23 +0000
3422@@ -0,0 +1,41 @@
3423+/*
3424+ * Copyright (C) 2016 Canonical Ltd.
3425+ *
3426+ * This program is free software: you can redistribute it and/or modify
3427+ * it under the terms of the GNU General Public License version 3 as
3428+ * published by the Free Software Foundation.
3429+ *
3430+ * This program is distributed in the hope that it will be useful,
3431+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3432+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3433+ * GNU General Public License for more details.
3434+ *
3435+ * You should have received a copy of the GNU General Public License
3436+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3437+ *
3438+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3439+ */
3440+
3441+#pragma once
3442+
3443+#include <QObject>
3444+
3445+#include <memory>
3446+
3447+#include "PropFindHandler.h"
3448+
3449+class RootsHandler : public PropFindHandler {
3450+ Q_OBJECT
3451+public:
3452+ RootsHandler(std::shared_ptr<DavProvider> const& provider,
3453+ unity::storage::provider::Context const& ctx);
3454+ ~RootsHandler();
3455+
3456+ boost::future<unity::storage::provider::ItemList> get_future();
3457+
3458+private:
3459+ boost::promise<unity::storage::provider::ItemList> promise_;
3460+
3461+protected:
3462+ void finish() override;
3463+};
3464
3465=== added file 'src/http_error.cpp'
3466--- src/http_error.cpp 1970-01-01 00:00:00 +0000
3467+++ src/http_error.cpp 2016-11-25 05:01:23 +0000
3468@@ -0,0 +1,181 @@
3469+/*
3470+ * Copyright (C) 2016 Canonical Ltd.
3471+ *
3472+ * This program is free software: you can redistribute it and/or modify
3473+ * it under the terms of the GNU General Public License version 3 as
3474+ * published by the Free Software Foundation.
3475+ *
3476+ * This program is distributed in the hope that it will be useful,
3477+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3478+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3479+ * GNU General Public License for more details.
3480+ *
3481+ * You should have received a copy of the GNU General Public License
3482+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3483+ *
3484+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3485+ */
3486+
3487+#include "http_error.h"
3488+
3489+#include <QNetworkReply>
3490+#include <QXmlDefaultHandler>
3491+#include <QXmlInputSource>
3492+#include <QXmlSimpleReader>
3493+#include <unity/storage/provider/Exceptions.h>
3494+
3495+#include <cassert>
3496+
3497+using namespace std;
3498+using namespace unity::storage::provider;
3499+
3500+namespace {
3501+
3502+class ErrorParser : public QXmlDefaultHandler
3503+{
3504+public:
3505+ bool startElement(QString const& namespace_uri, QString const& local_name,
3506+ QString const& qname, QXmlAttributes const& attrs) override;
3507+ bool endElement(QString const& namespace_uri, QString const& local_name,
3508+ QString const& qname) override;
3509+ bool characters(QString const& data) override;
3510+
3511+ string exception_;
3512+ string message_;
3513+
3514+private:
3515+ static QString const SABREDAV_NS;
3516+
3517+ QString char_data_;
3518+};
3519+
3520+QString const ErrorParser::SABREDAV_NS("http://sabredav.org/ns");
3521+
3522+bool ErrorParser::startElement(QString const& namespace_uri,
3523+ QString const& local_name,
3524+ QString const& qname,
3525+ QXmlAttributes const& attrs)
3526+{
3527+ Q_UNUSED(namespace_uri);
3528+ Q_UNUSED(local_name);
3529+ Q_UNUSED(qname);
3530+ Q_UNUSED(attrs);
3531+
3532+ char_data_.clear();
3533+ return true;
3534+}
3535+
3536+bool ErrorParser::endElement(QString const& namespace_uri,
3537+ QString const& local_name,
3538+ QString const& qname)
3539+{
3540+ Q_UNUSED(qname);
3541+ if (namespace_uri == SABREDAV_NS)
3542+ {
3543+ if (local_name == "exception")
3544+ {
3545+ exception_ = char_data_.toStdString();
3546+ }
3547+ else if (local_name == "message")
3548+ {
3549+ message_ = char_data_.toStdString();
3550+ }
3551+ }
3552+ char_data_.clear();
3553+ return true;
3554+}
3555+
3556+bool ErrorParser::characters(QString const& data)
3557+{
3558+ char_data_ += data;
3559+ return true;
3560+}
3561+
3562+std::string make_message(QNetworkReply *reply,
3563+ QByteArray body)
3564+{
3565+ if (body.isEmpty())
3566+ {
3567+ body = reply->readAll();
3568+ }
3569+ auto content_type = reply->header(QNetworkRequest::ContentTypeHeader).toString();
3570+ int semicolon = content_type.indexOf(';');
3571+ if (semicolon >= 0)
3572+ {
3573+ content_type = content_type.left(semicolon);
3574+ }
3575+ if (content_type == "text/plain")
3576+ {
3577+ return body.toStdString();
3578+ }
3579+ else if (content_type == "application/xml")
3580+ {
3581+ ErrorParser parser;
3582+ QXmlSimpleReader reader;
3583+ QXmlInputSource source;
3584+ reader.setContentHandler(&parser);
3585+ source.setData(body);
3586+ if (reader.parse(source)) {
3587+ return parser.exception_ + ": " + parser.message_;
3588+ }
3589+ }
3590+ // Fall back to returning the HTTP status string
3591+ return reply->attribute(
3592+ QNetworkRequest::HttpReasonPhraseAttribute).toString().toStdString();
3593+}
3594+
3595+}
3596+
3597+boost::exception_ptr translate_http_error(QNetworkReply *reply,
3598+ QByteArray const& body,
3599+ string const& item_id)
3600+{
3601+ assert(reply != nullptr);
3602+ assert(reply->isFinished());
3603+
3604+ auto status = reply->attribute(
3605+ QNetworkRequest::HttpStatusCodeAttribute).toInt();
3606+ if (status == 0)
3607+ {
3608+ return boost::copy_exception(
3609+ RemoteCommsException(reply->errorString().toStdString()));
3610+ }
3611+
3612+ auto message = make_message(reply, body);
3613+
3614+ switch (status)
3615+ {
3616+ case 400: // Bad Request
3617+ return boost::copy_exception(RemoteCommsException(message));
3618+
3619+ case 401: // Unauthorised
3620+ // This should be separate from Forbidden, triggering reauth.
3621+ case 403: // Forbidden
3622+ case 451: // Unavailable for Legal Reasons
3623+ return boost::copy_exception(PermissionException(message));
3624+
3625+ case 404: // Not found
3626+ case 410: // Gone
3627+ return boost::copy_exception(NotExistsException(message, item_id));
3628+
3629+ case 405: // Method Not Allowed
3630+ // MKCOL on an existing resource results in a 405 error
3631+ if (reply->request().attribute(QNetworkRequest::CustomVerbAttribute) == "MKCOL")
3632+ {
3633+ return boost::copy_exception(ExistsException(message, item_id, "fixme"));
3634+ }
3635+ break;
3636+
3637+ case 409: // Conflict
3638+ case 412: // Precondition failed
3639+ return boost::copy_exception(ConflictException(message));
3640+
3641+ case 507: // Insufficient Storage
3642+ return boost::copy_exception(QuotaException(message));
3643+
3644+ default:
3645+ break;
3646+ }
3647+ return boost::copy_exception(
3648+ UnknownException("HTTP " + to_string(status) + ": " + message));
3649+}
3650
3651=== added file 'src/http_error.h'
3652--- src/http_error.h 1970-01-01 00:00:00 +0000
3653+++ src/http_error.h 2016-11-25 05:01:23 +0000
3654@@ -0,0 +1,30 @@
3655+/*
3656+ * Copyright (C) 2016 Canonical Ltd.
3657+ *
3658+ * This program is free software: you can redistribute it and/or modify
3659+ * it under the terms of the GNU General Public License version 3 as
3660+ * published by the Free Software Foundation.
3661+ *
3662+ * This program is distributed in the hope that it will be useful,
3663+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3664+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3665+ * GNU General Public License for more details.
3666+ *
3667+ * You should have received a copy of the GNU General Public License
3668+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3669+ *
3670+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3671+ */
3672+
3673+#pragma once
3674+
3675+#include <boost/thread.hpp>
3676+
3677+class QByteArray;
3678+class QNetworkReply;
3679+
3680+static constexpr int MAX_ERROR_BODY_LENGTH = 64 * 1024;
3681+
3682+boost::exception_ptr translate_http_error(QNetworkReply *reply,
3683+ QByteArray const& body,
3684+ std::string const& item_id={});
3685
3686=== modified file 'src/item_id.cpp'
3687--- src/item_id.cpp 2016-09-18 23:12:40 +0000
3688+++ src/item_id.cpp 2016-11-25 05:01:23 +0000
3689@@ -1,3 +1,21 @@
3690+/*
3691+ * Copyright (C) 2016 Canonical Ltd.
3692+ *
3693+ * This program is free software: you can redistribute it and/or modify
3694+ * it under the terms of the GNU General Public License version 3 as
3695+ * published by the Free Software Foundation.
3696+ *
3697+ * This program is distributed in the hope that it will be useful,
3698+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3699+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3700+ * GNU General Public License for more details.
3701+ *
3702+ * You should have received a copy of the GNU General Public License
3703+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3704+ *
3705+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3706+ */
3707+
3708 #include "item_id.h"
3709
3710 #include <unity/storage/provider/Exceptions.h>
3711@@ -44,3 +62,44 @@
3712 }
3713 return item_id;
3714 }
3715+
3716+// Construct a possible ID for a named child of a parent ID
3717+string make_child_id(string const& parent_id, string const& name, bool is_folder)
3718+{
3719+ if (name == "." || name == "..")
3720+ {
3721+ throw InvalidArgumentException("Invalid name: " + name);
3722+ }
3723+
3724+ string item_id = parent_id;
3725+ if (item_id == ".")
3726+ {
3727+ item_id.clear();
3728+ }
3729+ else if (item_id.size() == 0 || item_id[item_id.size()-1] != '/')
3730+ {
3731+ item_id += '/';
3732+ }
3733+ item_id += QUrl::toPercentEncoding(QString::fromStdString(name)).toStdString();
3734+
3735+ if (is_folder)
3736+ {
3737+ item_id += '/';
3738+ }
3739+
3740+ return item_id;
3741+}
3742+
3743+bool is_folder(string const& item_id)
3744+{
3745+ auto size = item_id.size();
3746+ if (size == 0)
3747+ {
3748+ throw InvalidArgumentException("Invalid blank item ID");
3749+ }
3750+ if (size == 1 && item_id[0] == '.')
3751+ {
3752+ return true;
3753+ }
3754+ return item_id[size-1] == '/';
3755+}
3756
3757=== modified file 'src/item_id.h'
3758--- src/item_id.h 2016-09-18 10:00:45 +0000
3759+++ src/item_id.h 2016-11-25 05:01:23 +0000
3760@@ -1,3 +1,21 @@
3761+/*
3762+ * Copyright (C) 2016 Canonical Ltd.
3763+ *
3764+ * This program is free software: you can redistribute it and/or modify
3765+ * it under the terms of the GNU General Public License version 3 as
3766+ * published by the Free Software Foundation.
3767+ *
3768+ * This program is distributed in the hope that it will be useful,
3769+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3770+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3771+ * GNU General Public License for more details.
3772+ *
3773+ * You should have received a copy of the GNU General Public License
3774+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3775+ *
3776+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3777+ */
3778+
3779 #pragma once
3780
3781 #include <QUrl>
3782@@ -6,3 +24,8 @@
3783
3784 QUrl id_to_url(std::string const& item_id, QUrl const& base_url);
3785 std::string url_to_id(QUrl const& item_url, QUrl const& base_url);
3786+
3787+std::string make_child_id(std::string const& parent_id, std::string const& name,
3788+ bool is_folder=false);
3789+
3790+bool is_folder(std::string const& item_id);
3791
3792=== modified file 'src/main.cpp'
3793--- src/main.cpp 2016-09-22 06:40:07 +0000
3794+++ src/main.cpp 2016-11-25 05:01:23 +0000
3795@@ -1,3 +1,21 @@
3796+/*
3797+ * Copyright (C) 2016 Canonical Ltd.
3798+ *
3799+ * This program is free software: you can redistribute it and/or modify
3800+ * it under the terms of the GNU General Public License version 3 as
3801+ * published by the Free Software Foundation.
3802+ *
3803+ * This program is distributed in the hope that it will be useful,
3804+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3805+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3806+ * GNU General Public License for more details.
3807+ *
3808+ * You should have received a copy of the GNU General Public License
3809+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3810+ *
3811+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3812+ */
3813+
3814 #include <unity/storage/provider/Server.h>
3815
3816 #include "OwncloudProvider.h"
3817@@ -8,7 +26,7 @@
3818 int main(int argc, char **argv)
3819 {
3820 string const bus_name = "com.canonical.StorageFramework.Provider.OwnCloud";
3821- string const account_service_id = "google-drive-scope"; //"storage-provider-owncloud";
3822+ string const account_service_id = "storage-provider-owncloud";
3823
3824 Server<OwncloudProvider> server(bus_name, account_service_id);
3825 server.init(argc, argv);
3826
3827=== modified file 'tests/CMakeLists.txt'
3828--- tests/CMakeLists.txt 2016-09-23 03:28:37 +0000
3829+++ tests/CMakeLists.txt 2016-11-25 05:01:23 +0000
3830@@ -1,10 +1,4 @@
3831-# We add -g so we get debug info for the gtest stack frames with gdb.
3832-# The warnings are suppressed so we get a noise-free build for gtest and gmock.
3833-
3834-set(old_cxx_flags ${CMAKE_CXX_FLAGS})
3835-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wno-old-style-cast -Wno-missing-field-initializers")
3836 find_package(GMock)
3837-set(CMAKE_CXX_FLAGS ${old_cxx_flags})
3838
3839 configure_file(testsetup.h.in testsetup.h @ONLY)
3840 include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
3841@@ -15,6 +9,7 @@
3842 multistatus
3843 item_id
3844 davprovider
3845+ http_error
3846 )
3847
3848 set(UNIT_TEST_TARGETS "")
3849
3850=== modified file 'tests/davprovider/davprovider_test.cpp'
3851--- tests/davprovider/davprovider_test.cpp 2016-09-27 06:28:45 +0000
3852+++ tests/davprovider/davprovider_test.cpp 2016-11-25 05:01:23 +0000
3853@@ -1,26 +1,74 @@
3854+/*
3855+ * Copyright (C) 2016 Canonical Ltd.
3856+ *
3857+ * This program is free software: you can redistribute it and/or modify
3858+ * it under the terms of the GNU General Public License version 3 as
3859+ * published by the Free Software Foundation.
3860+ *
3861+ * This program is distributed in the hope that it will be useful,
3862+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3863+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3864+ * GNU General Public License for more details.
3865+ *
3866+ * You should have received a copy of the GNU General Public License
3867+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3868+ *
3869+ * Authored by: James Henstridge <james.henstridge@canonical.com>
3870+ */
3871+
3872 #include "../../src/DavProvider.h"
3873 #include <utils/DBusEnvironment.h>
3874 #include <utils/DavEnvironment.h>
3875 #include <utils/ProviderEnvironment.h>
3876 #include <testsetup.h>
3877
3878+#include <gtest/gtest.h>
3879 #include <QCoreApplication>
3880-#include <QFutureWatcher>
3881 #include <QNetworkAccessManager>
3882 #include <QNetworkRequest>
3883 #include <QSignalSpy>
3884 #include <QTemporaryDir>
3885-#include <unity/storage/qt/client/Account.h>
3886-#include <unity/storage/qt/client/Root.h>
3887+#include <QTimer>
3888+#include <unity/storage/qt/Account.h>
3889+#include <unity/storage/qt/Downloader.h>
3890+#include <unity/storage/qt/Item.h>
3891+#include <unity/storage/qt/ItemJob.h>
3892+#include <unity/storage/qt/ItemListJob.h>
3893+#include <unity/storage/qt/StorageError.h>
3894+#include <unity/storage/qt/Uploader.h>
3895+#include <unity/storage/qt/VoidJob.h>
3896
3897-#include <gtest/gtest.h>
3898+#include <fcntl.h>
3899+#include <sys/stat.h>
3900+#include <sys/types.h>
3901+#include <unistd.h>
3902+#include <utime.h>
3903+#include <algorithm>
3904
3905 using namespace std;
3906-using namespace unity::storage::provider;
3907-using namespace unity::storage::qt::client;
3908-using unity::storage::ItemType;
3909-
3910-static constexpr int SIGNAL_WAIT_TIME = 30000;
3911+using namespace unity::storage::qt;
3912+namespace provider = unity::storage::provider;
3913+
3914+void PrintTo(QString const& str, std::ostream* os)
3915+{
3916+ *os << "QString(\"" << str.toStdString() << "\")";
3917+}
3918+
3919+namespace
3920+{
3921+
3922+const string file_contents =
3923+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
3924+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut "
3925+ "enim ad minim veniam, quis nostrud exercitation ullamco laboris "
3926+ "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor "
3927+ "in reprehenderit in voluptate velit esse cillum dolore eu fugiat "
3928+ "nulla pariatur. Excepteur sint occaecat cupidatat non proident, "
3929+ "sunt in culpa qui officia deserunt mollit anim id est laborum.\n";
3930+
3931+constexpr int SIGNAL_WAIT_TIME = 30000;
3932+
3933+}
3934
3935 class TestDavProvider : public DavProvider
3936 {
3937@@ -30,7 +78,7 @@
3938 {
3939 }
3940
3941- QUrl base_url(Context const& ctx) const override
3942+ QUrl base_url(provider::Context const& ctx) const override
3943 {
3944 Q_UNUSED(ctx);
3945 return base_url_;
3946@@ -38,10 +86,11 @@
3947
3948 QNetworkReply *send_request(
3949 QNetworkRequest& request, QByteArray const& verb, QIODevice* data,
3950- unity::storage::provider::Context const& ctx) const override
3951+ provider::Context const& ctx) const override
3952 {
3953- const auto& creds = boost::get<PasswordCredentials>(ctx.credentials);
3954- const auto credentials = QByteArray::fromStdString(creds.username + ":" + creds.password);;
3955+ const auto& creds = boost::get<provider::PasswordCredentials>(ctx.credentials);
3956+ const auto credentials = QByteArray::fromStdString(creds.username + ":" +
3957+ creds.password);
3958 request.setRawHeader(QByteArrayLiteral("Authorization"),
3959 QByteArrayLiteral("Basic ") + credentials.toBase64());
3960 return network_->sendCustomRequest(request, verb, data);
3961@@ -64,8 +113,8 @@
3962
3963 dav_env_.reset(new DavEnvironment(tmp_dir_->path()));
3964 provider_env_.reset(new ProviderEnvironment(
3965- unique_ptr<ProviderBase>(new TestDavProvider(dav_env_->base_url())),
3966- 3, *dbus_env_));
3967+ make_shared<TestDavProvider>(dav_env_->base_url()),
3968+ 1, *dbus_env_));
3969 }
3970
3971 void TearDown() override
3972@@ -76,11 +125,47 @@
3973 dbus_env_.reset();
3974 }
3975
3976- shared_ptr<Account> get_client() const
3977+ Account get_client() const
3978 {
3979 return provider_env_->get_client();
3980 }
3981
3982+ string local_file(string const& path)
3983+ {
3984+ return tmp_dir_->path().toStdString() + "/" + path;
3985+ }
3986+
3987+ void make_file(string const& path)
3988+ {
3989+ string full_path = local_file(path);
3990+ int fd = open(full_path.c_str(), O_CREAT | O_EXCL, 0644);
3991+ ASSERT_GT(fd, 0);
3992+ ASSERT_EQ(0, close(fd));
3993+ }
3994+
3995+ void make_dir(string const& path)
3996+ {
3997+ string full_path = local_file(path);
3998+ ASSERT_EQ(0, mkdir(full_path.c_str(), 0755));
3999+ }
4000+
4001+ void touch_file(string const& path)
4002+ {
4003+ string full_path = local_file(path);
4004+ ASSERT_EQ(0, utime(full_path.c_str(), nullptr));
4005+ }
4006+
4007+ void offset_mtime(string const& path, int offset_seconds)
4008+ {
4009+ string full_path = local_file(path);
4010+ struct stat buf;
4011+ ASSERT_EQ(0, stat(full_path.c_str(), &buf));
4012+ struct utimbuf times;
4013+ times.actime = buf.st_atime + offset_seconds;
4014+ times.modtime = buf.st_mtime + offset_seconds;
4015+ ASSERT_EQ(0, utime(full_path.c_str(), &times));
4016+ }
4017+
4018 private:
4019 std::unique_ptr<DBusEnvironment> dbus_env_;
4020 std::unique_ptr<QTemporaryDir> tmp_dir_;
4021@@ -88,27 +173,753 @@
4022 std::unique_ptr<ProviderEnvironment> provider_env_;
4023 };
4024
4025+namespace
4026+{
4027+
4028+template <typename Job>
4029+void wait_for(Job* job)
4030+{
4031+ QSignalSpy spy(job, &Job::statusChanged);
4032+ while (job->status() == Job::Loading)
4033+ {
4034+ if (!spy.wait(SIGNAL_WAIT_TIME))
4035+ {
4036+ throw runtime_error("Wait for statusChanged signal timed out");
4037+ }
4038+ }
4039+}
4040+
4041+QList<Item> get_items(ItemListJob *job)
4042+{
4043+ QList<Item> items;
4044+ auto connection = QObject::connect(
4045+ job, &ItemListJob::itemsReady,
4046+ [&](QList<Item> const& new_items)
4047+ {
4048+ items.append(new_items);
4049+ });
4050+ try
4051+ {
4052+ wait_for(job);
4053+ }
4054+ catch (...)
4055+ {
4056+ QObject::disconnect(connection);
4057+ throw;
4058+ }
4059+ QObject::disconnect(connection);
4060+ return items;
4061+}
4062+
4063+Item get_root(Account const& account)
4064+{
4065+ unique_ptr<ItemListJob> job(account.roots());
4066+ QList<Item> roots = get_items(job.get());
4067+ if (job->status() != ItemListJob::Finished)
4068+ {
4069+ throw runtime_error("Account.roots(): " +
4070+ job->error().errorString().toStdString());
4071+ }
4072+ return roots.at(0);
4073+}
4074+
4075+}
4076+
4077 TEST_F(DavProviderTests, roots)
4078 {
4079 auto account = get_client();
4080- auto future = account->roots();
4081-
4082- QFutureWatcher<QVector<shared_ptr<Root>>> watcher;
4083- QSignalSpy spy(&watcher, &decltype(watcher)::finished);
4084- watcher.setFuture(account->roots());
4085- if (spy.count() == 0)
4086- {
4087- ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4088- }
4089-
4090- auto roots = watcher.result();
4091+
4092+ unique_ptr<ItemListJob> job(account.roots());
4093+ QList<Item> roots = get_items(job.get());
4094+ ASSERT_EQ(ItemListJob::Finished, job->status())
4095+ << job->error().errorString().toStdString();
4096+
4097 ASSERT_EQ(1, roots.size());
4098 auto item = roots[0];
4099- EXPECT_EQ(".", item->native_identity());
4100- EXPECT_EQ("Root", item->name());
4101- EXPECT_EQ(ItemType::root, item->type());
4102- EXPECT_TRUE(item->parent_ids().isEmpty());
4103- EXPECT_TRUE(item->last_modified_time().isValid());
4104+ EXPECT_EQ(".", item.itemId());
4105+ EXPECT_EQ("Root", item.name());
4106+ EXPECT_EQ(Item::Root, item.type());
4107+ EXPECT_TRUE(item.parentIds().isEmpty());
4108+ EXPECT_TRUE(item.lastModifiedTime().isValid());
4109+}
4110+
4111+TEST_F(DavProviderTests, list)
4112+{
4113+ auto account = get_client();
4114+ make_file("foo.txt");
4115+ make_file("bar.txt");
4116+ make_dir("folder");
4117+ make_file("I\u00F1t\u00EBrn\u00E2ti\u00F4n\u00E0liz\u00E6ti\u00F8n");
4118+
4119+ Item root = get_root(account);
4120+
4121+ unique_ptr<ItemListJob> job(root.list());
4122+ QList<Item> items = get_items(job.get());
4123+ ASSERT_EQ(ItemListJob::Finished, job->status())
4124+ << job->error().errorString().toStdString();
4125+
4126+ ASSERT_EQ(4, items.size());
4127+ sort(items.begin(), items.end(),
4128+ [](Item const& a, Item const& b) -> bool {
4129+ return a.itemId() < b.itemId();
4130+ });
4131+
4132+ EXPECT_EQ("I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0liz%C3%A6ti%C3%B8n", items[0].itemId());
4133+ EXPECT_EQ(".", items[0].parentIds().at(0));
4134+ EXPECT_EQ("I\u00F1t\u00EBrn\u00E2ti\u00F4n\u00E0liz\u00E6ti\u00F8n", items[0].name());
4135+ EXPECT_EQ(Item::File, items[0].type());
4136+
4137+ EXPECT_EQ("bar.txt", items[1].itemId());
4138+ EXPECT_EQ(".", items[1].parentIds().at(0));
4139+ EXPECT_EQ("bar.txt", items[1].name());
4140+ EXPECT_EQ(Item::File, items[1].type());
4141+
4142+ EXPECT_EQ("folder/", items[2].itemId());
4143+ EXPECT_EQ(".", items[2].parentIds().at(0));
4144+ EXPECT_EQ("folder", items[2].name());
4145+ EXPECT_EQ(Item::Folder, items[2].type());
4146+
4147+ EXPECT_EQ("foo.txt", items[3].itemId());
4148+ EXPECT_EQ(".", items[3].parentIds().at(0));
4149+ EXPECT_EQ("foo.txt", items[3].name());
4150+ EXPECT_EQ(Item::File, items[3].type());
4151+}
4152+
4153+TEST_F(DavProviderTests, lookup)
4154+{
4155+ auto account = get_client();
4156+ make_file("foo.txt");
4157+
4158+ Item root = get_root(account);
4159+
4160+ unique_ptr<ItemListJob> job(root.lookup("foo.txt"));
4161+ QList<Item> items = get_items(job.get());
4162+ ASSERT_EQ(ItemListJob::Finished, job->status())
4163+ << job->error().errorString().toStdString();
4164+
4165+ ASSERT_EQ(1, items.size());
4166+ EXPECT_EQ("foo.txt", items[0].itemId());
4167+ EXPECT_EQ(".", items[0].parentIds().at(0));
4168+ EXPECT_EQ("foo.txt", items[0].name());
4169+ EXPECT_EQ(Item::File, items[0].type());
4170+}
4171+
4172+TEST_F(DavProviderTests, metadata)
4173+{
4174+ auto account = get_client();
4175+ make_file("foo.txt");
4176+
4177+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4178+ wait_for(job.get());
4179+ ASSERT_EQ(ItemJob::Finished, job->status())
4180+ << job->error().errorString().toStdString();
4181+
4182+ Item item = job->item();
4183+ EXPECT_EQ("foo.txt", item.itemId());
4184+ EXPECT_EQ(".", item.parentIds().at(0));
4185+ EXPECT_EQ("foo.txt", item.name());
4186+ EXPECT_EQ(Item::File, item.type());
4187+}
4188+
4189+TEST_F(DavProviderTests, metadata_not_found)
4190+{
4191+ auto account = get_client();
4192+
4193+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4194+ wait_for(job.get());
4195+ ASSERT_EQ(ItemJob::Error, job->status());
4196+
4197+ auto error = job->error();
4198+ EXPECT_EQ(StorageError::NotExists, error.type());
4199+ EXPECT_TRUE(error.message().startsWith("Sabre\\DAV\\Exception\\NotFound: "))
4200+ << error.message().toStdString();
4201+}
4202+
4203+TEST_F(DavProviderTests, create_folder)
4204+{
4205+ auto account = get_client();
4206+
4207+ Item root = get_root(account);
4208+ unique_ptr<ItemJob> job(root.createFolder("folder"));
4209+ wait_for(job.get());
4210+ ASSERT_EQ(ItemJob::Finished, job->status())
4211+ << job->error().errorString().toStdString();
4212+
4213+ Item folder = job->item();
4214+ EXPECT_EQ("folder/", folder.itemId());
4215+ EXPECT_EQ(".", folder.parentIds().at(0));
4216+ EXPECT_EQ("folder", folder.name());
4217+ EXPECT_EQ(Item::Folder, folder.type());
4218+}
4219+
4220+TEST_F(DavProviderTests, create_folder_reserved_chars)
4221+{
4222+ auto account = get_client();
4223+
4224+ Item root = get_root(account);
4225+ unique_ptr<ItemJob> job(root.createFolder("14:19"));
4226+ wait_for(job.get());
4227+ ASSERT_EQ(ItemJob::Finished, job->status())
4228+ << job->error().errorString().toStdString();
4229+
4230+ Item folder = job->item();
4231+ EXPECT_EQ("14:19/", folder.itemId());
4232+ EXPECT_EQ(".", folder.parentIds().at(0));
4233+ EXPECT_EQ("14:19", folder.name());
4234+ EXPECT_EQ(Item::Folder, folder.type());
4235+}
4236+
4237+TEST_F(DavProviderTests, create_folder_overwrite_file)
4238+{
4239+ auto account = get_client();
4240+ make_file("folder");
4241+
4242+ Item root = get_root(account);
4243+ unique_ptr<ItemJob> job(root.createFolder("folder"));
4244+ wait_for(job.get());
4245+ ASSERT_EQ(ItemJob::Error, job->status());
4246+
4247+ auto error = job->error();
4248+ EXPECT_EQ(StorageError::Exists, error.type());
4249+ EXPECT_EQ("Sabre\\DAV\\Exception\\MethodNotAllowed: The resource you tried to create already exists", error.message());
4250+}
4251+
4252+TEST_F(DavProviderTests, create_folder_overwrite_folder)
4253+{
4254+ auto account = get_client();
4255+ ASSERT_EQ(0, mkdir(local_file("folder").c_str(), 0755));
4256+
4257+ Item root = get_root(account);
4258+ unique_ptr<ItemJob> job(root.createFolder("folder"));
4259+ wait_for(job.get());
4260+ ASSERT_EQ(ItemJob::Error, job->status());
4261+
4262+ auto error = job->error();
4263+ EXPECT_EQ(StorageError::Exists, error.type());
4264+ EXPECT_EQ("Sabre\\DAV\\Exception\\MethodNotAllowed: The resource you tried to create already exists", error.message());
4265+}
4266+
4267+TEST_F(DavProviderTests, create_file)
4268+{
4269+ int const segments = 50;
4270+
4271+ auto account = get_client();
4272+ Item root = get_root(account);
4273+
4274+ unique_ptr<Uploader> uploader(
4275+ root.createFile("filename.txt", Item::ErrorIfConflict,
4276+ file_contents.size() * segments, "text/plain"));
4277+
4278+ int count = 0;
4279+ QTimer timer;
4280+ timer.setSingleShot(false);
4281+ timer.setInterval(10);
4282+ QObject::connect(&timer, &QTimer::timeout, [&] {
4283+ uploader->write(&file_contents[0], file_contents.size());
4284+ count++;
4285+ if (count == segments)
4286+ {
4287+ uploader->close();
4288+ }
4289+ });
4290+
4291+ QSignalSpy spy(uploader.get(), &Uploader::statusChanged);
4292+ timer.start();
4293+ while (uploader->status() == Uploader::Loading ||
4294+ uploader->status() == Uploader::Ready)
4295+ {
4296+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4297+ }
4298+ ASSERT_EQ(Uploader::Finished, uploader->status())
4299+ << uploader->error().errorString().toStdString();
4300+
4301+ auto file = uploader->item();
4302+ EXPECT_EQ("filename.txt", file.itemId());
4303+ ASSERT_EQ(1, file.parentIds().size());
4304+ EXPECT_EQ(".", file.parentIds().at(0));
4305+ EXPECT_EQ("filename.txt", file.name());
4306+ EXPECT_NE(0, file.etag().size());
4307+ EXPECT_EQ(Item::File, file.type());
4308+ EXPECT_EQ(int64_t(file_contents.size() * segments), file.sizeInBytes());
4309+
4310+ string full_path = local_file("filename.txt");
4311+ struct stat buf;
4312+ ASSERT_EQ(0, stat(full_path.c_str(), &buf));
4313+ EXPECT_EQ(off_t(file_contents.size() * segments), buf.st_size);
4314+}
4315+
4316+TEST_F(DavProviderTests, create_file_over_existing_file)
4317+{
4318+ auto account = get_client();
4319+ make_file("foo.txt");
4320+
4321+ Item root = get_root(account);
4322+ unique_ptr<Uploader> uploader(
4323+ root.createFile("foo.txt", Item::ErrorIfConflict, 0, "text/plain"));
4324+ QSignalSpy spy(uploader.get(), &Uploader::statusChanged);
4325+ while (uploader->status() == Uploader::Loading)
4326+ {
4327+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4328+ }
4329+
4330+ uploader->close();
4331+ while (uploader->status() == Uploader::Ready)
4332+ {
4333+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4334+ }
4335+ ASSERT_EQ(Uploader::Error, uploader->status());
4336+
4337+ auto error = uploader->error();
4338+ EXPECT_EQ(StorageError::Conflict, error.type());
4339+ EXPECT_EQ("Sabre\\DAV\\Exception\\PreconditionFailed: An If-None-Match header was specified, but the ETag matched (or * was specified).", error.message());
4340+}
4341+
4342+TEST_F(DavProviderTests, create_file_overwrite_existing)
4343+{
4344+ auto account = get_client();
4345+ make_file("foo.txt");
4346+
4347+ Item root = get_root(account);
4348+ unique_ptr<Uploader> uploader(
4349+ root.createFile("foo.txt", Item::IgnoreConflict, 0, "text/plain"));
4350+ QSignalSpy spy(uploader.get(), &Uploader::statusChanged);
4351+ while (uploader->status() == Uploader::Loading)
4352+ {
4353+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4354+ }
4355+
4356+ uploader->close();
4357+ while (uploader->status() == Uploader::Ready)
4358+ {
4359+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4360+ }
4361+ ASSERT_EQ(Uploader::Finished, uploader->status())
4362+ << uploader->error().errorString().toStdString();
4363+}
4364+
4365+TEST_F(DavProviderTests, update)
4366+{
4367+ int const segments = 50;
4368+
4369+ auto account = get_client();
4370+ make_file("foo.txt");
4371+ // Offset to ensure modification time changes
4372+ offset_mtime("foo.txt", -10);
4373+
4374+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4375+ wait_for(job.get());
4376+ ASSERT_EQ(ItemJob::Finished, job->status())
4377+ << job->error().errorString().toStdString();
4378+
4379+ auto file = job->item();
4380+ QString old_etag = file.etag();
4381+
4382+ unique_ptr<Uploader> uploader(
4383+ file.createUploader(Item::ErrorIfConflict,
4384+ file_contents.size() * segments));
4385+
4386+ int count = 0;
4387+ QTimer timer;
4388+ timer.setSingleShot(false);
4389+ timer.setInterval(10);
4390+ QObject::connect(&timer, &QTimer::timeout, [&] {
4391+ uploader->write(&file_contents[0], file_contents.size());
4392+ count++;
4393+ if (count == segments)
4394+ {
4395+ uploader->close();
4396+ }
4397+ });
4398+
4399+ QSignalSpy spy(uploader.get(), &Uploader::statusChanged);
4400+ timer.start();
4401+ while (uploader->status() == Uploader::Loading ||
4402+ uploader->status() == Uploader::Ready)
4403+ {
4404+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4405+ }
4406+ ASSERT_EQ(Uploader::Finished, uploader->status())
4407+ << uploader->error().errorString().toStdString();
4408+
4409+ file = uploader->item();
4410+ EXPECT_NE(old_etag, file.etag());
4411+ EXPECT_EQ(int64_t(file_contents.size() * segments), file.sizeInBytes());
4412+}
4413+
4414+TEST_F(DavProviderTests, update_conflict)
4415+{
4416+ auto account = get_client();
4417+ make_file("foo.txt");
4418+ // Offset to ensure modification time changes
4419+ offset_mtime("foo.txt", -10);
4420+
4421+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4422+ wait_for(job.get());
4423+ ASSERT_EQ(ItemJob::Finished, job->status())
4424+ << job->error().errorString().toStdString();
4425+
4426+ // Change the file after metadata has been looked up
4427+ touch_file("foo.txt");
4428+
4429+ auto file = job->item();
4430+ unique_ptr<Uploader> uploader(
4431+ file.createUploader(Item::ErrorIfConflict, 0));
4432+ QSignalSpy spy(uploader.get(), &Uploader::statusChanged);
4433+ while (uploader->status() == Uploader::Loading)
4434+ {
4435+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4436+ }
4437+
4438+ uploader->close();
4439+ while (uploader->status() == Uploader::Ready)
4440+ {
4441+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4442+ }
4443+ ASSERT_EQ(Uploader::Error, uploader->status());
4444+
4445+ auto error = uploader->error();
4446+ EXPECT_EQ(StorageError::Conflict, error.type());
4447+ EXPECT_EQ("Sabre\\DAV\\Exception\\PreconditionFailed: An If-Match header was specified, but none of the specified the ETags matched.", error.message());
4448+}
4449+
4450+TEST_F(DavProviderTests, upload_short_write)
4451+{
4452+ auto account = get_client();
4453+
4454+ Item root = get_root(account);
4455+ unique_ptr<Uploader> uploader(
4456+ root.createFile("foo.txt", Item::ErrorIfConflict, 1000, "text/plain"));
4457+ QSignalSpy spy(uploader.get(), &Uploader::statusChanged);
4458+ while (uploader->status() == Uploader::Loading)
4459+ {
4460+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4461+ }
4462+
4463+ uploader->close();
4464+ while (uploader->status() == Uploader::Ready)
4465+ {
4466+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4467+ }
4468+ ASSERT_EQ(Uploader::Error, uploader->status());
4469+
4470+ auto error = uploader->error();
4471+ EXPECT_EQ(StorageError::RemoteCommsError, error.type());
4472+ EXPECT_EQ("Unknown error", error.message());
4473+}
4474+
4475+TEST_F(DavProviderTests, upload_cancel)
4476+{
4477+ auto account = get_client();
4478+
4479+ Item root = get_root(account);
4480+ unique_ptr<Uploader> uploader(
4481+ root.createFile("foo.txt", Item::ErrorIfConflict, 1000, "text/plain"));
4482+ QSignalSpy spy(uploader.get(), &Uploader::statusChanged);
4483+ while (uploader->status() == Uploader::Loading)
4484+ {
4485+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4486+ }
4487+
4488+ uploader->cancel();
4489+ while (uploader->status() == Uploader::Ready)
4490+ {
4491+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4492+ }
4493+ ASSERT_EQ(Uploader::Cancelled, uploader->status());
4494+
4495+ auto error = uploader->error();
4496+ EXPECT_EQ(StorageError::Cancelled, error.type());
4497+ EXPECT_EQ("Uploader::cancel(): upload was cancelled", error.message());
4498+}
4499+
4500+TEST_F(DavProviderTests, download)
4501+{
4502+ int const segments = 1000;
4503+ string large_contents;
4504+ large_contents.reserve(file_contents.size() * segments);
4505+ for (int i = 0; i < segments; i++)
4506+ {
4507+ large_contents += file_contents;
4508+ }
4509+ string const full_path = local_file("foo.txt");
4510+ {
4511+ int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644);
4512+ ASSERT_GT(fd, 0);
4513+ ASSERT_EQ(ssize_t(large_contents.size()), write(fd, &large_contents[0], large_contents.size())) << strerror(errno);
4514+ ASSERT_EQ(0, close(fd));
4515+ }
4516+
4517+ auto account = get_client();
4518+
4519+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4520+ wait_for(job.get());
4521+ ASSERT_EQ(ItemJob::Finished, job->status())
4522+ << job->error().errorString().toStdString();
4523+
4524+ auto file = job->item();
4525+
4526+ unique_ptr<Downloader> downloader(
4527+ file.createDownloader(Item::ErrorIfConflict));
4528+
4529+ int64_t n_read = 0;
4530+ QObject::connect(downloader.get(), &QIODevice::readyRead,
4531+ [&]() {
4532+ auto bytes = downloader->readAll();
4533+ string const expected = large_contents.substr(
4534+ n_read, bytes.size());
4535+ EXPECT_EQ(expected, bytes.toStdString());
4536+ n_read += bytes.size();
4537+ });
4538+ QSignalSpy read_finished_spy(
4539+ downloader.get(), &QIODevice::readChannelFinished);
4540+ ASSERT_TRUE(read_finished_spy.wait(SIGNAL_WAIT_TIME));
4541+
4542+ QSignalSpy status_spy(downloader.get(), &Downloader::statusChanged);
4543+ downloader->close();
4544+ while (downloader->status() == Downloader::Ready)
4545+ {
4546+ ASSERT_TRUE(status_spy.wait(SIGNAL_WAIT_TIME));
4547+ }
4548+ ASSERT_EQ(Downloader::Finished, downloader->status())
4549+ << downloader->error().errorString().toStdString();
4550+
4551+ EXPECT_EQ(int64_t(large_contents.size()), n_read);
4552+}
4553+
4554+TEST_F(DavProviderTests, download_short_read)
4555+{
4556+ int const segments = 1000;
4557+ {
4558+ string full_path = local_file("foo.txt");
4559+ int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644);
4560+ ASSERT_GT(fd, 0);
4561+ for (int i = 0; i < segments; i++)
4562+ {
4563+ ASSERT_EQ(ssize_t(file_contents.size()), write(fd, &file_contents[0], file_contents.size())) << strerror(errno);
4564+ }
4565+ ASSERT_EQ(0, close(fd));
4566+ }
4567+
4568+ auto account = get_client();
4569+
4570+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4571+ wait_for(job.get());
4572+ ASSERT_EQ(ItemJob::Finished, job->status())
4573+ << job->error().errorString().toStdString();
4574+
4575+ auto file = job->item();
4576+ unique_ptr<Downloader> downloader(
4577+ file.createDownloader(Item::ErrorIfConflict));
4578+
4579+ QSignalSpy spy(downloader.get(), &Downloader::statusChanged);
4580+ while (downloader->status() == Downloader::Loading)
4581+ {
4582+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4583+ }
4584+
4585+ downloader->close();
4586+ while (downloader->status() == Downloader::Ready)
4587+ {
4588+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
4589+ }
4590+ ASSERT_EQ(Downloader::Error, downloader->status());
4591+
4592+ auto error = downloader->error();
4593+ EXPECT_EQ(StorageError::LogicError, error.type());
4594+ EXPECT_EQ("finish called before all data sent", error.message());
4595+}
4596+
4597+TEST_F(DavProviderTests, download_not_found)
4598+{
4599+ auto account = get_client();
4600+ make_file("foo.txt");
4601+
4602+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4603+ wait_for(job.get());
4604+ ASSERT_EQ(ItemJob::Finished, job->status())
4605+ << job->error().errorString().toStdString();
4606+
4607+ auto file = job->item();
4608+
4609+ ASSERT_EQ(0, unlink(local_file("foo.txt").c_str()));
4610+
4611+ unique_ptr<Downloader> downloader(
4612+ file.createDownloader(Item::ErrorIfConflict));
4613+
4614+ QObject::connect(downloader.get(), &QIODevice::readyRead,
4615+ [&]() {
4616+ downloader->readAll();
4617+ });
4618+ QSignalSpy read_finished_spy(
4619+ downloader.get(), &QIODevice::readChannelFinished);
4620+ ASSERT_TRUE(read_finished_spy.wait(SIGNAL_WAIT_TIME));
4621+
4622+ QSignalSpy status_spy(downloader.get(), &Downloader::statusChanged);
4623+ downloader->close();
4624+ while (downloader->status() == Downloader::Ready)
4625+ {
4626+ ASSERT_TRUE(status_spy.wait(SIGNAL_WAIT_TIME));
4627+ }
4628+ ASSERT_EQ(Downloader::Error, downloader->status());
4629+
4630+ auto error = downloader->error();
4631+ EXPECT_EQ(StorageError::NotExists, error.type());
4632+ EXPECT_TRUE(error.message().startsWith("Sabre\\DAV\\Exception\\NotFound: "))
4633+ << error.message().toStdString();
4634+}
4635+
4636+TEST_F(DavProviderTests, delete_item)
4637+{
4638+ auto account = get_client();
4639+ make_file("foo.txt");
4640+
4641+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4642+ wait_for(job.get());
4643+ ASSERT_EQ(ItemJob::Finished, job->status())
4644+ << job->error().errorString().toStdString();
4645+
4646+ Item item = job->item();
4647+ unique_ptr<VoidJob> delete_job(item.deleteItem());
4648+ wait_for(delete_job.get());
4649+ ASSERT_EQ(VoidJob::Finished, delete_job->status())
4650+ << delete_job->error().errorString().toStdString();
4651+
4652+ struct stat buf;
4653+ EXPECT_EQ(-1, stat(local_file("foo.txt").c_str(), &buf));
4654+ EXPECT_EQ(ENOENT, errno);
4655+}
4656+
4657+TEST_F(DavProviderTests, delete_item_not_found)
4658+{
4659+ auto account = get_client();
4660+ make_file("foo.txt");
4661+
4662+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4663+ wait_for(job.get());
4664+ ASSERT_EQ(ItemJob::Finished, job->status())
4665+ << job->error().errorString().toStdString();
4666+
4667+ Item item = job->item();
4668+
4669+ ASSERT_EQ(0, unlink(local_file("foo.txt").c_str()));
4670+
4671+ unique_ptr<VoidJob> delete_job(item.deleteItem());
4672+ wait_for(delete_job.get());
4673+ ASSERT_EQ(VoidJob::Error, delete_job->status());
4674+
4675+ auto error = delete_job->error();
4676+ EXPECT_EQ(StorageError::NotExists, error.type());
4677+ EXPECT_TRUE(error.message().startsWith("Sabre\\DAV\\Exception\\NotFound: "))
4678+ << error.message().toStdString();
4679+}
4680+
4681+TEST_F(DavProviderTests, move)
4682+{
4683+ string const full_path = local_file("foo.txt");
4684+ {
4685+ int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644);
4686+ ASSERT_GT(fd, 0);
4687+ ASSERT_EQ(ssize_t(file_contents.size()), write(fd, &file_contents[0], file_contents.size())) << strerror(errno);
4688+ ASSERT_EQ(0, close(fd));
4689+ }
4690+
4691+ auto account = get_client();
4692+ Item root = get_root(account);
4693+
4694+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4695+ wait_for(job.get());
4696+ ASSERT_EQ(ItemJob::Finished, job->status())
4697+ << job->error().errorString().toStdString();
4698+
4699+ Item item = job->item();
4700+
4701+ job.reset(item.move(root, "new-name.txt"));
4702+ wait_for(job.get());
4703+ ASSERT_EQ(ItemJob::Finished, job->status())
4704+ << job->error().errorString().toStdString();
4705+
4706+ item = job->item();
4707+ EXPECT_EQ("new-name.txt", item.itemId());
4708+ ASSERT_EQ(1, item.parentIds().size());
4709+ EXPECT_EQ(".", item.parentIds().at(0));
4710+ EXPECT_EQ("new-name.txt", item.name());
4711+ EXPECT_NE(0, item.etag().size());
4712+ EXPECT_EQ(Item::File, item.type());
4713+ EXPECT_EQ(int64_t(file_contents.size()), item.sizeInBytes());
4714+
4715+ // The old file no longer exists
4716+ struct stat buf;
4717+ EXPECT_EQ(-1, stat(full_path.c_str(), &buf));
4718+ EXPECT_EQ(ENOENT, errno);
4719+
4720+ // And the new one does
4721+ string const new_path = local_file("new-name.txt");
4722+ EXPECT_EQ(0, stat(new_path.c_str(), &buf));
4723+ EXPECT_EQ(off_t(file_contents.size()), buf.st_size);
4724+
4725+ // And its has the expected contents
4726+ {
4727+ int fd = open(new_path.c_str(), O_RDONLY);
4728+ ASSERT_GT(fd, 0);
4729+ string contents(file_contents.size(), '\0');
4730+ EXPECT_EQ(ssize_t(file_contents.size()), read(fd, &contents[0], contents.size()));
4731+ EXPECT_EQ(0, close(fd));
4732+ EXPECT_EQ(file_contents, contents);
4733+ }
4734+}
4735+
4736+TEST_F(DavProviderTests, copy)
4737+{
4738+ string const full_path = local_file("foo.txt");
4739+ {
4740+ int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644);
4741+ ASSERT_GT(fd, 0);
4742+ ASSERT_EQ(ssize_t(file_contents.size()), write(fd, &file_contents[0], file_contents.size())) << strerror(errno);
4743+ ASSERT_EQ(0, close(fd));
4744+ }
4745+
4746+ auto account = get_client();
4747+ Item root = get_root(account);
4748+
4749+ unique_ptr<ItemJob> job(account.get("foo.txt"));
4750+ wait_for(job.get());
4751+ ASSERT_EQ(ItemJob::Finished, job->status())
4752+ << job->error().errorString().toStdString();
4753+
4754+ Item item = job->item();
4755+
4756+ job.reset(item.copy(root, "new-name.txt"));
4757+ wait_for(job.get());
4758+ ASSERT_EQ(ItemJob::Finished, job->status())
4759+ << job->error().errorString().toStdString();
4760+
4761+ item = job->item();
4762+ EXPECT_EQ("new-name.txt", item.itemId());
4763+ ASSERT_EQ(1, item.parentIds().size());
4764+ EXPECT_EQ(".", item.parentIds().at(0));
4765+ EXPECT_EQ("new-name.txt", item.name());
4766+ EXPECT_NE(0, item.etag().size());
4767+ EXPECT_EQ(Item::File, item.type());
4768+ EXPECT_EQ(int64_t(file_contents.size()), item.sizeInBytes());
4769+
4770+ // The old file still exists
4771+ struct stat buf;
4772+ EXPECT_EQ(0, stat(full_path.c_str(), &buf));
4773+
4774+ // And the new one does too
4775+ string const new_path = local_file("new-name.txt");
4776+ EXPECT_EQ(0, stat(new_path.c_str(), &buf));
4777+ EXPECT_EQ(off_t(file_contents.size()), buf.st_size);
4778+
4779+ // And its has the expected contents
4780+ {
4781+ int fd = open(new_path.c_str(), O_RDONLY);
4782+ ASSERT_GT(fd, 0);
4783+ string contents(file_contents.size(), '\0');
4784+ EXPECT_EQ(ssize_t(file_contents.size()), read(fd, &contents[0], contents.size()));
4785+ EXPECT_EQ(0, close(fd));
4786+ EXPECT_EQ(file_contents, contents);
4787+ }
4788 }
4789
4790 int main(int argc, char**argv)
4791
4792=== added directory 'tests/http_error'
4793=== added file 'tests/http_error/CMakeLists.txt'
4794--- tests/http_error/CMakeLists.txt 1970-01-01 00:00:00 +0000
4795+++ tests/http_error/CMakeLists.txt 2016-11-25 05:01:23 +0000
4796@@ -0,0 +1,8 @@
4797+add_executable(http_error_test http_error_test.cpp)
4798+target_link_libraries(http_error_test
4799+ dav-provider-lib
4800+ testutils
4801+ Qt5::Test
4802+ gtest
4803+)
4804+add_test(http_error_test http_error_test)
4805
4806=== added file 'tests/http_error/http_error_test.cpp'
4807--- tests/http_error/http_error_test.cpp 1970-01-01 00:00:00 +0000
4808+++ tests/http_error/http_error_test.cpp 2016-11-25 05:01:23 +0000
4809@@ -0,0 +1,278 @@
4810+/*
4811+ * Copyright (C) 2016 Canonical Ltd.
4812+ *
4813+ * This program is free software: you can redistribute it and/or modify
4814+ * it under the terms of the GNU General Public License version 3 as
4815+ * published by the Free Software Foundation.
4816+ *
4817+ * This program is distributed in the hope that it will be useful,
4818+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4819+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4820+ * GNU General Public License for more details.
4821+ *
4822+ * You should have received a copy of the GNU General Public License
4823+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4824+ *
4825+ * Authored by: James Henstridge <james.henstridge@canonical.com>
4826+ */
4827+
4828+#include "../../src/http_error.h"
4829+
4830+#include <gtest/gtest.h>
4831+#include <QBuffer>
4832+#include <QByteArray>
4833+#include <QCoreApplication>
4834+#include <QNetworkReply>
4835+#include <QNetworkRequest>
4836+#include <unity/storage/provider/Exceptions.h>
4837+
4838+using namespace std;
4839+using namespace unity::storage::provider;
4840+
4841+class FakeReply : public QNetworkReply
4842+{
4843+public:
4844+ FakeReply(QString const& method, int status, QString const& reason,
4845+ QString const& content_type, QByteArray const& body)
4846+ {
4847+ QNetworkRequest request;
4848+ request.setAttribute(QNetworkRequest::CustomVerbAttribute, method);
4849+ setRequest(request);
4850+
4851+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
4852+ setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reason);
4853+
4854+ setHeader(QNetworkRequest::ContentTypeHeader, content_type);
4855+ setHeader(QNetworkRequest::ContentLengthHeader, body.size());
4856+ buffer_.setData(body);
4857+ buffer_.open(QIODevice::ReadOnly);
4858+ open(QIODevice::ReadOnly);
4859+ setFinished(true);
4860+ }
4861+
4862+ FakeReply(QNetworkReply::NetworkError code, QString const& message)
4863+ {
4864+ setError(code, message);
4865+ setFinished(true);
4866+ }
4867+
4868+ void abort() override
4869+ {
4870+ close();
4871+ }
4872+
4873+ qint64 bytesAvailable() const override
4874+ {
4875+ return QNetworkReply::bytesAvailable() + buffer_.bytesAvailable();
4876+ }
4877+
4878+ bool isSequential() const override
4879+ {
4880+ return true;
4881+ }
4882+
4883+ qint64 size() const override
4884+ {
4885+ return buffer_.size();
4886+ }
4887+
4888+ qint64 readData(char *data, qint64 length) override
4889+ {
4890+ return buffer_.read(data, length);
4891+ }
4892+
4893+private:
4894+ QBuffer buffer_;
4895+};
4896+
4897+template <typename E>
4898+E expect_throw(boost::exception_ptr p)
4899+{
4900+ try
4901+ {
4902+ boost::rethrow_exception(p);
4903+ }
4904+ catch (E const& e)
4905+ {
4906+ return e;
4907+ }
4908+ throw std::runtime_error("No exception thrown");
4909+}
4910+
4911+
4912+TEST(HttpErrorTests, network_error)
4913+{
4914+ FakeReply reply(QNetworkReply::HostNotFoundError, "Host not found");
4915+
4916+ auto e = expect_throw<RemoteCommsException>(
4917+ translate_http_error(&reply, QByteArray()));
4918+ EXPECT_EQ("Host not found", e.error_message());
4919+}
4920+
4921+TEST(HttpErrorTests, text_plain_body)
4922+{
4923+ FakeReply reply("GET", 400, "Bad Request",
4924+ "text/plain;charset=US-ASCII", "error message");
4925+ auto e = expect_throw<RemoteCommsException>(
4926+ translate_http_error(&reply, QByteArray()));
4927+ EXPECT_EQ("error message", e.error_message());
4928+}
4929+
4930+TEST(HttpErrorTests, application_xml_body)
4931+{
4932+ static const char xml[] = R"(<?xml version="1.0" encoding="utf-8"?>
4933+<d:error xmlns:d='DAV:' xmlns:s='http://sabredav.org/ns'>
4934+ <s:exception>exception_name</s:exception>
4935+ <s:message>error message</s:message>
4936+ <s:sabredav-version>1.8.12</s:sabredav-version>
4937+</d:error>
4938+)";
4939+ FakeReply reply("GET", 400, "Bad Request",
4940+ "application/xml;charset=utf-8", xml);
4941+
4942+ auto e = expect_throw<RemoteCommsException>(
4943+ translate_http_error(&reply, QByteArray()));
4944+ EXPECT_EQ("exception_name: error message", e.error_message());
4945+}
4946+
4947+TEST(HttpErrorTests, fallback_body)
4948+{
4949+ FakeReply reply("GET", 400, "Bad Request",
4950+ "application/octet-stream", "foo");
4951+ auto e = expect_throw<RemoteCommsException>(
4952+ translate_http_error(&reply, QByteArray()));
4953+ EXPECT_EQ("Bad Request", e.error_message());
4954+}
4955+
4956+TEST(HttpErrorTests, body_provided_separately)
4957+{
4958+ static const char xml[] = R"(<?xml version="1.0" encoding="utf-8"?>
4959+<d:error xmlns:d='DAV:' xmlns:s='http://sabredav.org/ns'>
4960+ <s:exception>exception_name</s:exception>
4961+ <s:message>error message</s:message>
4962+ <s:sabredav-version>1.8.12</s:sabredav-version>
4963+</d:error>
4964+)";
4965+ FakeReply reply("GET", 400, "Bad Request",
4966+ "application/xml;charset=utf-8", "foo");
4967+
4968+ auto e = expect_throw<RemoteCommsException>(
4969+ translate_http_error(&reply, xml));
4970+ EXPECT_EQ("exception_name: error message", e.error_message());
4971+}
4972+
4973+TEST(HttpErrorTests, 401)
4974+{
4975+ FakeReply reply("GET", 401, "Unauthorised", "text/plain", "message");
4976+
4977+ auto e = expect_throw<PermissionException>(
4978+ translate_http_error(&reply, QByteArray()));
4979+ EXPECT_EQ("message", e.error_message());
4980+}
4981+
4982+TEST(HttpErrorTests, 403)
4983+{
4984+ FakeReply reply("GET", 403, "Forbidden", "text/plain", "message");
4985+
4986+ auto e = expect_throw<PermissionException>(
4987+ translate_http_error(&reply, QByteArray()));
4988+ EXPECT_EQ("message", e.error_message());
4989+}
4990+
4991+TEST(HttpErrorTests, 451)
4992+{
4993+ FakeReply reply("GET", 451, "Unavailable for Legal Reasons",
4994+ "text/plain", "message");
4995+
4996+ auto e = expect_throw<PermissionException>(
4997+ translate_http_error(&reply, QByteArray()));
4998+ EXPECT_EQ("message", e.error_message());
4999+}
5000+
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches