Merge branch '402-blog-client-implementation' into 'master'
Blog Client with Factories and Validator This implements a simple initial blog client that covers the basic blog actions, but no deletion/removal of blogs, yet. Classes for Blogs and Blog Post Headers have been introduced along with the associated factories. A `BlogPostValidator` has been added that validates incoming blog posts. Closes #402 Closes #404 See merge request !212
92
briar-android/artwork/trust-indicator.svg
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="49"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 49.000004 20"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="trust-indicator.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="15.839192"
|
||||||
|
inkscape:cx="19.828141"
|
||||||
|
inkscape:cy="4.1791031"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="993"
|
||||||
|
inkscape:window-x="1440"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-216.17711,-507.04154)">
|
||||||
|
<g
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#b7b7b7;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
id="text4136"
|
||||||
|
transform="matrix(1,0,0,0.90497738,-18.96574,55.694085)">
|
||||||
|
<path
|
||||||
|
d="m 250.64285,514.07648 0,-2.275 -2.55,0 0,-3.8 2.55,0 0,-2.275 -2.55,0 0,-4.225 -2.375,0 0,4.225 -3.15,0 0,-4.225 -2.375,0 0,4.225 -2.55,0 0,2.275 2.55,0 0,3.8 -2.55,0 0,2.275 2.55,0 0,4 2.375,0 0,-4 3.15,0 0,4 2.375,0 0,-4 2.55,0 z m -4.925,-2.275 -3.15,0 0,-3.8 3.15,0 0,3.8 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-family:Titillium;-inkscape-font-specification:'Titillium Semi-Bold';fill:#b7b7b7;fill-opacity:1"
|
||||||
|
id="path4745"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(1,0,0,0.90497738,-3.4657389,55.694085)"
|
||||||
|
id="g4755"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#b7b7b7;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4757"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-family:Titillium;-inkscape-font-specification:'Titillium Semi-Bold';fill:#b7b7b7;fill-opacity:1"
|
||||||
|
d="m 250.64285,514.07648 0,-2.275 -2.55,0 0,-3.8 2.55,0 0,-2.275 -2.55,0 0,-4.225 -2.375,0 0,4.225 -3.15,0 0,-4.225 -2.375,0 0,4.225 -2.55,0 0,2.275 2.55,0 0,3.8 -2.55,0 0,2.275 2.55,0 0,4 2.375,0 0,-4 3.15,0 0,4 2.375,0 0,-4 2.55,0 z m -4.925,-2.275 -3.15,0 0,-3.8 3.15,0 0,3.8 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#b7b7b7;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
id="g4139"
|
||||||
|
transform="matrix(1,0,0,0.90497738,12.034262,55.694085)">
|
||||||
|
<path
|
||||||
|
d="m 250.64285,514.07648 0,-2.275 -2.55,0 0,-3.8 2.55,0 0,-2.275 -2.55,0 0,-4.225 -2.375,0 0,4.225 -3.15,0 0,-4.225 -2.375,0 0,4.225 -2.55,0 0,2.275 2.55,0 0,3.8 -2.55,0 0,2.275 2.55,0 0,4 2.375,0 0,-4 3.15,0 0,4 2.375,0 0,-4 2.55,0 z m -4.925,-2.275 -3.15,0 0,-3.8 3.15,0 0,3.8 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-family:Titillium;-inkscape-font-specification:'Titillium Semi-Bold';fill:#b7b7b7;fill-opacity:1"
|
||||||
|
id="path4141"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1009 B |
|
Before Width: | Height: | Size: 362 B |
|
Before Width: | Height: | Size: 708 B |
|
Before Width: | Height: | Size: 713 B |
|
Before Width: | Height: | Size: 531 B |
|
Before Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
40
briar-android/res/drawable/trust_indicator_anonymous.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="40dp"
|
||||||
|
android:height="15dp"
|
||||||
|
android:viewportHeight="20"
|
||||||
|
android:viewportWidth="49">
|
||||||
|
<path
|
||||||
|
android:fillColor="#b7b7b7"
|
||||||
|
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
|
||||||
|
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
|
||||||
|
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
|
||||||
|
L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194
|
||||||
|
L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797
|
||||||
|
L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797
|
||||||
|
L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
|
||||||
|
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
|
||||||
|
L10.5752,8.38194 L10.5752,11.8208 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#b7b7b7"
|
||||||
|
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
|
||||||
|
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
|
||||||
|
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
|
||||||
|
L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194
|
||||||
|
L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797
|
||||||
|
L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797
|
||||||
|
L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
|
||||||
|
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
|
||||||
|
L26.0752,8.38194 L26.0752,11.8208 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#b7b7b7"
|
||||||
|
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
|
||||||
|
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
|
||||||
|
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959
|
||||||
|
L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194
|
||||||
|
L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797
|
||||||
|
L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797
|
||||||
|
L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797
|
||||||
|
L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194
|
||||||
|
L41.5752,8.38194 L41.5752,11.8208 Z"/>
|
||||||
|
</vector>
|
||||||
40
briar-android/res/drawable/trust_indicator_unknown.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="40dp"
|
||||||
|
android:height="15dp"
|
||||||
|
android:viewportHeight="20"
|
||||||
|
android:viewportWidth="49">
|
||||||
|
<path
|
||||||
|
android:fillColor="#c34032"
|
||||||
|
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
|
||||||
|
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
|
||||||
|
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
|
||||||
|
L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194
|
||||||
|
L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797
|
||||||
|
L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797
|
||||||
|
L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
|
||||||
|
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
|
||||||
|
L10.5752,8.38194 L10.5752,11.8208 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#b7b7b7"
|
||||||
|
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
|
||||||
|
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
|
||||||
|
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
|
||||||
|
L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194
|
||||||
|
L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797
|
||||||
|
L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797
|
||||||
|
L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
|
||||||
|
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
|
||||||
|
L26.0752,8.38194 L26.0752,11.8208 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#b7b7b7"
|
||||||
|
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
|
||||||
|
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
|
||||||
|
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959
|
||||||
|
L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194
|
||||||
|
L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797
|
||||||
|
L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797
|
||||||
|
L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797
|
||||||
|
L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194
|
||||||
|
L41.5752,8.38194 L41.5752,11.8208 Z"/>
|
||||||
|
</vector>
|
||||||
40
briar-android/res/drawable/trust_indicator_unverified.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="40dp"
|
||||||
|
android:height="15dp"
|
||||||
|
android:viewportHeight="20"
|
||||||
|
android:viewportWidth="49">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fcd53a"
|
||||||
|
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
|
||||||
|
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
|
||||||
|
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
|
||||||
|
L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194
|
||||||
|
L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797
|
||||||
|
L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797
|
||||||
|
L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
|
||||||
|
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
|
||||||
|
L10.5752,8.38194 L10.5752,11.8208 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#fcd53a"
|
||||||
|
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
|
||||||
|
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
|
||||||
|
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
|
||||||
|
L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194
|
||||||
|
L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797
|
||||||
|
L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797
|
||||||
|
L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
|
||||||
|
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
|
||||||
|
L26.0752,8.38194 L26.0752,11.8208 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#b7b7b7"
|
||||||
|
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
|
||||||
|
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
|
||||||
|
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959
|
||||||
|
L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194
|
||||||
|
L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797
|
||||||
|
L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797
|
||||||
|
L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797
|
||||||
|
L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194
|
||||||
|
L41.5752,8.38194 L41.5752,11.8208 Z"/>
|
||||||
|
</vector>
|
||||||
40
briar-android/res/drawable/trust_indicator_verified.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="40dp"
|
||||||
|
android:height="15dp"
|
||||||
|
android:viewportHeight="20"
|
||||||
|
android:viewportWidth="49">
|
||||||
|
<path
|
||||||
|
android:fillColor="#7fac49"
|
||||||
|
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
|
||||||
|
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
|
||||||
|
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
|
||||||
|
L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194
|
||||||
|
L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797
|
||||||
|
L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797
|
||||||
|
L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
|
||||||
|
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
|
||||||
|
L10.5752,8.38194 L10.5752,11.8208 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#7fac49"
|
||||||
|
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
|
||||||
|
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
|
||||||
|
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
|
||||||
|
L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194
|
||||||
|
L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797
|
||||||
|
L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797
|
||||||
|
L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
|
||||||
|
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
|
||||||
|
L26.0752,8.38194 L26.0752,11.8208 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#7fac49"
|
||||||
|
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
|
||||||
|
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
|
||||||
|
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959
|
||||||
|
L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194
|
||||||
|
L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797
|
||||||
|
L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797
|
||||||
|
L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797
|
||||||
|
L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194
|
||||||
|
L41.5752,8.38194 L41.5752,11.8208 Z"/>
|
||||||
|
</vector>
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<de.hdodenhof.circleimageview.CircleImageView
|
|
||||||
android:id="@+id/avatarView"
|
|
||||||
style="@style/BriarAvatar"
|
|
||||||
android:layout_width="@dimen/listitem_picture_size"
|
|
||||||
android:layout_height="@dimen/listitem_picture_size"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
|
||||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
|
||||||
tools:src="@drawable/ic_launcher"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/statusView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:src="@drawable/identity_anonymous"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/nameView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginEnd="@dimen/margin_small"
|
|
||||||
android:layout_marginLeft="@dimen/listitem_text_left_margin"
|
|
||||||
android:layout_marginRight="@dimen/margin_small"
|
|
||||||
android:layout_marginStart="@dimen/listitem_text_left_margin"
|
|
||||||
android:layout_toLeftOf="@id/statusView"
|
|
||||||
android:layout_toStartOf="@id/statusView"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:text="@string/anonymous"
|
|
||||||
android:textSize="@dimen/text_size_medium"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -131,6 +131,9 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignBaseline="@id/btn_reply"
|
android:layout_alignBaseline="@id/btn_reply"
|
||||||
android:layout_toLeftOf="@id/btn_reply"
|
android:layout_toLeftOf="@id/btn_reply"
|
||||||
|
android:layout_toRightOf="@+id/trustIndicator"
|
||||||
|
android:gravity="right|end"
|
||||||
|
android:maxLines="1"
|
||||||
android:padding="@dimen/margin_medium"
|
android:padding="@dimen/margin_medium"
|
||||||
android:textSize="@dimen/text_size_tiny"
|
android:textSize="@dimen/text_size_tiny"
|
||||||
tools:text="2 replies"/>
|
tools:text="2 replies"/>
|
||||||
@@ -140,13 +143,23 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignBaseline="@id/replies"
|
android:layout_alignBaseline="@id/replies"
|
||||||
android:layout_toLeftOf="@id/replies"
|
|
||||||
android:layout_toRightOf="@id/avatar"
|
android:layout_toRightOf="@id/avatar"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textSize="@dimen/text_size_tiny"
|
android:textSize="@dimen/text_size_tiny"
|
||||||
tools:text="09:09 John Smith"/>
|
tools:text="09:09 John Smith"/>
|
||||||
|
|
||||||
|
<org.briarproject.android.util.TrustIndicatorView
|
||||||
|
android:id="@+id/trustIndicator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignBottom="@+id/date"
|
||||||
|
android:layout_alignTop="@+id/date"
|
||||||
|
android:layout_marginLeft="@dimen/margin_small"
|
||||||
|
android:layout_marginStart="@dimen/margin_small"
|
||||||
|
android:layout_toRightOf="@+id/date"
|
||||||
|
tools:src="@drawable/trust_indicator_verified"/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/bottom_divider"
|
android:id="@+id/bottom_divider"
|
||||||
style="@style/Divider.ForumList"
|
style="@style/Divider.ForumList"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.briarproject.android.BriarActivity;
|
|||||||
import org.briarproject.android.api.AndroidNotificationManager;
|
import org.briarproject.android.api.AndroidNotificationManager;
|
||||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||||
import org.briarproject.android.util.BriarRecyclerView;
|
import org.briarproject.android.util.BriarRecyclerView;
|
||||||
|
import org.briarproject.android.util.TrustIndicatorView;
|
||||||
import org.briarproject.api.sync.GroupId;
|
import org.briarproject.api.sync.GroupId;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
@@ -275,6 +276,7 @@ public class ForumActivity extends BriarActivity implements
|
|||||||
public final TextView textView, lvlText, dateText, repliesText;
|
public final TextView textView, lvlText, dateText, repliesText;
|
||||||
public final View[] lvls;
|
public final View[] lvls;
|
||||||
public final ImageView avatar;
|
public final ImageView avatar;
|
||||||
|
final TrustIndicatorView trust;
|
||||||
public final View chevron, replyButton;
|
public final View chevron, replyButton;
|
||||||
public final ViewGroup cell;
|
public final ViewGroup cell;
|
||||||
public final View bottomDivider;
|
public final View bottomDivider;
|
||||||
@@ -295,6 +297,7 @@ public class ForumActivity extends BriarActivity implements
|
|||||||
lvls[i] = v.findViewById(nestedLineIds[i]);
|
lvls[i] = v.findViewById(nestedLineIds[i]);
|
||||||
}
|
}
|
||||||
avatar = (ImageView) v.findViewById(R.id.avatar);
|
avatar = (ImageView) v.findViewById(R.id.avatar);
|
||||||
|
trust = (TrustIndicatorView) v.findViewById(R.id.trustIndicator);
|
||||||
chevron = v.findViewById(R.id.chevron);
|
chevron = v.findViewById(R.id.chevron);
|
||||||
replyButton = v.findViewById(R.id.btn_reply);
|
replyButton = v.findViewById(R.id.btn_reply);
|
||||||
cell = (ViewGroup) v.findViewById(R.id.forum_cell);
|
cell = (ViewGroup) v.findViewById(R.id.forum_cell);
|
||||||
@@ -513,6 +516,7 @@ public class ForumActivity extends BriarActivity implements
|
|||||||
ui.dateText.setText(DateUtils
|
ui.dateText.setText(DateUtils
|
||||||
.getRelativeTimeSpanString(ForumActivity.this,
|
.getRelativeTimeSpanString(ForumActivity.this,
|
||||||
data.getTimestamp()) + " " + data.getAuthor());
|
data.getTimestamp()) + " " + data.getAuthor());
|
||||||
|
ui.trust.setTrustLevel(data.getStatus());
|
||||||
|
|
||||||
int replies = getReplyCount(data);
|
int replies = getReplyCount(data);
|
||||||
if (replies == 0) {
|
if (replies == 0) {
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
package org.briarproject.android.forum;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.briarproject.R;
|
|
||||||
import org.briarproject.android.util.AuthorView;
|
|
||||||
import org.briarproject.android.util.LayoutUtils;
|
|
||||||
import org.briarproject.api.forum.ForumPostHeader;
|
|
||||||
import org.briarproject.api.identity.Author;
|
|
||||||
import org.briarproject.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import static android.view.Gravity.CENTER_HORIZONTAL;
|
|
||||||
import static android.view.Gravity.CENTER_VERTICAL;
|
|
||||||
import static android.widget.LinearLayout.HORIZONTAL;
|
|
||||||
import static android.widget.LinearLayout.VERTICAL;
|
|
||||||
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
|
|
||||||
|
|
||||||
class ForumAdapter extends ArrayAdapter<ForumItem> {
|
|
||||||
|
|
||||||
private final int pad;
|
|
||||||
|
|
||||||
ForumAdapter(Context ctx) {
|
|
||||||
super(ctx, android.R.layout.simple_expandable_list_item_1,
|
|
||||||
new ArrayList<ForumItem>());
|
|
||||||
pad = LayoutUtils.getPadding(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
ForumItem item = getItem(position);
|
|
||||||
ForumPostHeader header = item.getHeader();
|
|
||||||
Context ctx = getContext();
|
|
||||||
Resources res = ctx.getResources();
|
|
||||||
|
|
||||||
LinearLayout layout = new LinearLayout(ctx);
|
|
||||||
layout.setOrientation(VERTICAL);
|
|
||||||
layout.setGravity(CENTER_HORIZONTAL);
|
|
||||||
if (!header.isRead())
|
|
||||||
layout.setBackgroundColor(res.getColor(R.color.unread_background));
|
|
||||||
|
|
||||||
LinearLayout headerLayout = new LinearLayout(ctx);
|
|
||||||
headerLayout.setOrientation(HORIZONTAL);
|
|
||||||
headerLayout.setGravity(CENTER_VERTICAL);
|
|
||||||
|
|
||||||
AuthorView authorView = new AuthorView(ctx);
|
|
||||||
authorView.setLayoutParams(WRAP_WRAP_1);
|
|
||||||
authorView.setPadding(0, pad, pad, pad);
|
|
||||||
Author author = header.getAuthor();
|
|
||||||
if (author == null) {
|
|
||||||
authorView.init(null, null, header.getAuthorStatus());
|
|
||||||
} else {
|
|
||||||
authorView.init(author.getName(), author.getId(),
|
|
||||||
header.getAuthorStatus());
|
|
||||||
}
|
|
||||||
headerLayout.addView(authorView);
|
|
||||||
|
|
||||||
TextView date = new TextView(ctx);
|
|
||||||
date.setPadding(pad, pad, pad, pad);
|
|
||||||
long timestamp = header.getTimestamp();
|
|
||||||
date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
|
||||||
headerLayout.addView(date);
|
|
||||||
layout.addView(headerLayout);
|
|
||||||
|
|
||||||
if (item.getBody() == null) {
|
|
||||||
TextView ellipsis = new TextView(ctx);
|
|
||||||
ellipsis.setPadding(pad, 0, pad, pad);
|
|
||||||
ellipsis.setText("\u2026");
|
|
||||||
layout.addView(ellipsis);
|
|
||||||
} else if (header.getContentType().equals("text/plain")) {
|
|
||||||
TextView text = new TextView(ctx);
|
|
||||||
text.setPadding(pad, 0, pad, pad);
|
|
||||||
text.setText(StringUtils.fromUtf8(item.getBody()));
|
|
||||||
layout.addView(text);
|
|
||||||
} else {
|
|
||||||
ImageButton attachment = new ImageButton(ctx);
|
|
||||||
attachment.setPadding(pad, 0, pad, pad);
|
|
||||||
attachment.setImageResource(R.drawable.content_attachment);
|
|
||||||
layout.addView(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.android.forum;
|
package org.briarproject.android.forum;
|
||||||
|
|
||||||
import org.briarproject.api.forum.ForumPostHeader;
|
import org.briarproject.api.forum.ForumPostHeader;
|
||||||
|
import org.briarproject.api.identity.Author.Status;
|
||||||
import org.briarproject.api.identity.AuthorId;
|
import org.briarproject.api.identity.AuthorId;
|
||||||
import org.briarproject.api.sync.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
|
||||||
@@ -12,23 +13,25 @@ public class ForumEntry {
|
|||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
private final String author;
|
private final String author;
|
||||||
private final AuthorId authorId;
|
private final AuthorId authorId;
|
||||||
|
private Status status;
|
||||||
private boolean isShowingDescendants = true;
|
private boolean isShowingDescendants = true;
|
||||||
private boolean isRead = true;
|
private boolean isRead = true;
|
||||||
|
|
||||||
public ForumEntry(ForumPostHeader h, String text, int level) {
|
public ForumEntry(ForumPostHeader h, String text, int level) {
|
||||||
this(h.getId(), text, level, h.getTimestamp(), h.getAuthor().getName(),
|
this(h.getId(), text, level, h.getTimestamp(), h.getAuthor().getName(),
|
||||||
h.getAuthor().getId());
|
h.getAuthor().getId(), h.getAuthorStatus());
|
||||||
this.isRead = h.isRead();
|
this.isRead = h.isRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForumEntry(MessageId messageId, String text, int level,
|
public ForumEntry(MessageId messageId, String text, int level,
|
||||||
long timestamp, String author, AuthorId authorId) {
|
long timestamp, String author, AuthorId authorId, Status status) {
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.level = level;
|
this.level = level;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
this.authorId = authorId;
|
this.authorId = authorId;
|
||||||
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getText() {
|
public String getText() {
|
||||||
@@ -51,6 +54,10 @@ public class ForumEntry {
|
|||||||
return authorId;
|
return authorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Status getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isShowingDescendants() {
|
public boolean isShowingDescendants() {
|
||||||
return isShowingDescendants;
|
return isShowingDescendants;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.api.identity.Author.Status.UNVERIFIED;
|
||||||
|
|
||||||
public class ForumTestControllerImpl implements ForumController {
|
public class ForumTestControllerImpl implements ForumController {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -115,7 +117,7 @@ public class ForumTestControllerImpl implements ForumController {
|
|||||||
forumEntries[e] =
|
forumEntries[e] =
|
||||||
new ForumEntry(new MessageId(b), SAGA.substring(0, i[e]),
|
new ForumEntry(new MessageId(b), SAGA.substring(0, i[e]),
|
||||||
l[e], timestamp, AUTHORS[authorIndex],
|
l[e], timestamp, AUTHORS[authorIndex],
|
||||||
AUTHOR_ID[authorIndex]);
|
AUTHOR_ID[authorIndex], UNVERIFIED);
|
||||||
}
|
}
|
||||||
LOG.info("forum entries: " + forumEntries.length);
|
LOG.info("forum entries: " + forumEntries.length);
|
||||||
resultHandler.onResult(true);
|
resultHandler.onResult(true);
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
package org.briarproject.android.util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.briarproject.R;
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.api.identity.Author;
|
|
||||||
import org.briarproject.api.identity.AuthorId;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import im.delight.android.identicons.IdenticonDrawable;
|
|
||||||
|
|
||||||
public class AuthorView extends FrameLayout {
|
|
||||||
|
|
||||||
private ImageView avatarView;
|
|
||||||
private TextView nameView;
|
|
||||||
private ImageView statusView;
|
|
||||||
|
|
||||||
public AuthorView(Context ctx) {
|
|
||||||
super(ctx);
|
|
||||||
|
|
||||||
initViews();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthorView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
|
|
||||||
initViews();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthorView(Context context, AttributeSet attrs,
|
|
||||||
int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
|
|
||||||
initViews();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initViews() {
|
|
||||||
if (isInEditMode())
|
|
||||||
return;
|
|
||||||
|
|
||||||
View v = LayoutInflater.from(getContext()).inflate(
|
|
||||||
R.layout.author_view, this, true);
|
|
||||||
|
|
||||||
avatarView = (ImageView) v.findViewById(R.id.avatarView);
|
|
||||||
nameView = (TextView) v.findViewById(R.id.nameView);
|
|
||||||
statusView = (ImageView) v.findViewById(R.id.statusView);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(String name, AuthorId id, Author.Status status) {
|
|
||||||
if (name == null) {
|
|
||||||
nameView.setText(R.string.anonymous);
|
|
||||||
} else {
|
|
||||||
nameView.setText(name);
|
|
||||||
avatarView.setImageDrawable(
|
|
||||||
new IdenticonDrawable(id.getBytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(status) {
|
|
||||||
case ANONYMOUS:
|
|
||||||
statusView.setImageResource(R.drawable.identity_anonymous);
|
|
||||||
break;
|
|
||||||
case UNKNOWN:
|
|
||||||
statusView.setImageResource(R.drawable.identity_unknown);
|
|
||||||
break;
|
|
||||||
case UNVERIFIED:
|
|
||||||
statusView.setImageResource(R.drawable.identity_unverified);
|
|
||||||
break;
|
|
||||||
case VERIFIED:
|
|
||||||
statusView.setImageResource(R.drawable.identity_verified);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.android.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import org.briarproject.R;
|
||||||
|
import org.briarproject.api.identity.Author.Status;
|
||||||
|
|
||||||
|
public class TrustIndicatorView extends ImageView {
|
||||||
|
|
||||||
|
public TrustIndicatorView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrustIndicatorView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrustIndicatorView(Context context, AttributeSet attrs,
|
||||||
|
int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrustLevel(Status status) {
|
||||||
|
int res;
|
||||||
|
switch (status) {
|
||||||
|
case ANONYMOUS:
|
||||||
|
res = R.drawable.trust_indicator_anonymous;
|
||||||
|
break;
|
||||||
|
case UNVERIFIED:
|
||||||
|
res = R.drawable.trust_indicator_unverified;
|
||||||
|
break;
|
||||||
|
case VERIFIED:
|
||||||
|
res = R.drawable.trust_indicator_verified;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res = R.drawable.trust_indicator_unknown;
|
||||||
|
}
|
||||||
|
setImageDrawable(ContextCompat.getDrawable(getContext(), res));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,14 +7,16 @@ apply plugin: 'witness'
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile "com.google.dagger:dagger:2.0.2"
|
compile "com.google.dagger:dagger:2.0.2"
|
||||||
compile 'com.google.dagger:dagger-compiler:2.0.2'
|
compile 'com.google.dagger:dagger-compiler:2.0.2'
|
||||||
|
compile 'org.jetbrains:annotations-java5:15.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyVerification {
|
dependencyVerification {
|
||||||
verify = [
|
verify = [
|
||||||
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||||
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
||||||
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
'org.jetbrains:annotations-java5:c84e6e9947f802ec2183bdc415dd496df02a749cac92e805f697e60f628a1e24',
|
||||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||||
|
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
||||||
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
32
briar-api/src/org/briarproject/api/blogs/Blog.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package org.briarproject.api.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.forum.Forum;
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.sync.Group;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class Blog extends Forum {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final String description;
|
||||||
|
@NotNull
|
||||||
|
private final Author author;
|
||||||
|
|
||||||
|
public Blog(@NotNull Group group, @NotNull String name,
|
||||||
|
@NotNull String description, @NotNull Author author) {
|
||||||
|
super(group, name, null);
|
||||||
|
|
||||||
|
this.description = description;
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Author getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
briar-api/src/org/briarproject/api/blogs/BlogConstants.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package org.briarproject.api.blogs;
|
||||||
|
|
||||||
|
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
|
|
||||||
|
public interface BlogConstants {
|
||||||
|
|
||||||
|
/** The maximum length of a blogs's name in UTF-8 bytes. */
|
||||||
|
int MAX_BLOG_TITLE_LENGTH = 100;
|
||||||
|
|
||||||
|
/** The length of a blogs's description in UTF-8 bytes. */
|
||||||
|
int MAX_BLOG_DESC_LENGTH = 240;
|
||||||
|
|
||||||
|
/** The maximum length of a blog post's content type in UTF-8 bytes. */
|
||||||
|
int MAX_CONTENT_TYPE_LENGTH = 50;
|
||||||
|
|
||||||
|
/** The length of a blog post's title in UTF-8 bytes. */
|
||||||
|
int MAX_BLOG_POST_TITLE_LENGTH = 100;
|
||||||
|
|
||||||
|
/** The length of a blog post's teaser in UTF-8 bytes. */
|
||||||
|
int MAX_BLOG_POST_TEASER_LENGTH = 240;
|
||||||
|
|
||||||
|
/** The maximum length of a blog post's body in bytes. */
|
||||||
|
int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
||||||
|
|
||||||
|
// Metadata keys
|
||||||
|
String KEY_DESCRIPTION = "description";
|
||||||
|
String KEY_TITLE = "title";
|
||||||
|
String KEY_TEASER = "teaser";
|
||||||
|
String KEY_HAS_BODY = "hasBody";
|
||||||
|
String KEY_TIMESTAMP = "timestamp";
|
||||||
|
String KEY_PARENT = "parent";
|
||||||
|
String KEY_AUTHOR_ID = "id";
|
||||||
|
String KEY_AUTHOR_NAME = "name";
|
||||||
|
String KEY_PUBLIC_KEY = "publicKey";
|
||||||
|
String KEY_AUTHOR = "author";
|
||||||
|
String KEY_CONTENT_TYPE = "contentType";
|
||||||
|
String KEY_READ = "read";
|
||||||
|
|
||||||
|
}
|
||||||
17
briar-api/src/org/briarproject/api/blogs/BlogFactory.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package org.briarproject.api.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.sync.Group;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface BlogFactory {
|
||||||
|
|
||||||
|
/** Creates a blog with the given name, description and author. */
|
||||||
|
Blog createBlog(@NotNull String name, @NotNull String description,
|
||||||
|
@NotNull Author author);
|
||||||
|
|
||||||
|
/** Parses a blog with the given Group and description */
|
||||||
|
Blog parseBlog(@NotNull Group g, @NotNull String description)
|
||||||
|
throws FormatException;
|
||||||
|
}
|
||||||
48
briar-api/src/org/briarproject/api/blogs/BlogManager.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package org.briarproject.api.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.db.DbException;
|
||||||
|
import org.briarproject.api.db.Transaction;
|
||||||
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.api.sync.ClientId;
|
||||||
|
import org.briarproject.api.sync.GroupId;
|
||||||
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface BlogManager {
|
||||||
|
|
||||||
|
/** Returns the unique ID of the blog client. */
|
||||||
|
ClientId getClientId();
|
||||||
|
|
||||||
|
/** Creates a new Blog. */
|
||||||
|
Blog addBlog(LocalAuthor localAuthor, String name, String description)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
|
/** Stores a local blog post. */
|
||||||
|
void addLocalPost(BlogPost p) throws DbException;
|
||||||
|
|
||||||
|
/** Returns the blog with the given ID. */
|
||||||
|
Blog getBlog(GroupId g) throws DbException;
|
||||||
|
|
||||||
|
/** Returns the blog with the given ID. */
|
||||||
|
Blog getBlog(Transaction txn, GroupId g) throws DbException;
|
||||||
|
|
||||||
|
/** Returns all blogs to which the localAuthor created. */
|
||||||
|
Collection<Blog> getBlogs(LocalAuthor localAuthor) throws DbException;
|
||||||
|
|
||||||
|
/** Returns all blogs to which the user subscribes. */
|
||||||
|
Collection<Blog> getBlogs() throws DbException;
|
||||||
|
|
||||||
|
/** Returns the body of the blog post with the given ID. */
|
||||||
|
@Nullable
|
||||||
|
byte[] getPostBody(MessageId m) throws DbException;
|
||||||
|
|
||||||
|
/** Returns the headers of all posts in the given blog. */
|
||||||
|
Collection<BlogPostHeader> getPostHeaders(GroupId g) throws DbException;
|
||||||
|
|
||||||
|
/** Marks a blog post as read or unread. */
|
||||||
|
void setReadFlag(MessageId m, boolean read) throws DbException;
|
||||||
|
|
||||||
|
}
|
||||||
43
briar-api/src/org/briarproject/api/blogs/BlogPost.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.api.blogs;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import org.briarproject.api.forum.ForumPost;
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.sync.Message;
|
||||||
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
|
||||||
|
public class BlogPost extends ForumPost {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String title;
|
||||||
|
@NotNull
|
||||||
|
private final String teaser;
|
||||||
|
private final boolean hasBody;
|
||||||
|
|
||||||
|
public BlogPost(@Nullable String title, @NotNull String teaser,
|
||||||
|
boolean hasBody, @NotNull Message message,
|
||||||
|
@Nullable MessageId parent, @NotNull Author author,
|
||||||
|
@NotNull String contentType) {
|
||||||
|
super(message, parent, author, contentType);
|
||||||
|
|
||||||
|
this.title = title;
|
||||||
|
this.teaser = teaser;
|
||||||
|
this.hasBody = hasBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getTeaser() {
|
||||||
|
return teaser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBody() {
|
||||||
|
return hasBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.briarproject.api.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.api.sync.GroupId;
|
||||||
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
public interface BlogPostFactory {
|
||||||
|
|
||||||
|
BlogPost createBlogPost(@NotNull GroupId groupId, @Nullable String title,
|
||||||
|
@NotNull String teaser, long timestamp, @Nullable MessageId parent,
|
||||||
|
@NotNull LocalAuthor author, @NotNull String contentType,
|
||||||
|
@Nullable byte[] body)
|
||||||
|
throws FormatException, GeneralSecurityException;
|
||||||
|
}
|
||||||
43
briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.api.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.clients.PostHeader;
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.identity.Author.Status;
|
||||||
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class BlogPostHeader extends PostHeader {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String title;
|
||||||
|
@NotNull
|
||||||
|
private final String teaser;
|
||||||
|
private final boolean hasBody;
|
||||||
|
|
||||||
|
public BlogPostHeader(@Nullable String title, @NotNull String teaser,
|
||||||
|
boolean hasBody, @NotNull MessageId id,
|
||||||
|
@Nullable MessageId parentId, long timestamp,
|
||||||
|
@NotNull Author author, @NotNull Status authorStatus,
|
||||||
|
@NotNull String contentType, boolean read) {
|
||||||
|
super(id, parentId, timestamp, author, authorStatus, contentType, read);
|
||||||
|
|
||||||
|
this.title = title;
|
||||||
|
this.teaser = teaser;
|
||||||
|
this.hasBody = hasBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getTeaser() {
|
||||||
|
return teaser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBody() {
|
||||||
|
return hasBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
briar-api/src/org/briarproject/api/clients/PostHeader.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package org.briarproject.api.clients;
|
||||||
|
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
|
||||||
|
public abstract class PostHeader {
|
||||||
|
|
||||||
|
private final MessageId id;
|
||||||
|
private final MessageId parentId;
|
||||||
|
private final long timestamp;
|
||||||
|
private final Author author;
|
||||||
|
private final Author.Status authorStatus;
|
||||||
|
private final String contentType;
|
||||||
|
private final boolean read;
|
||||||
|
|
||||||
|
public PostHeader(MessageId id, MessageId parentId, long timestamp,
|
||||||
|
Author author, Author.Status authorStatus, String contentType,
|
||||||
|
boolean read) {
|
||||||
|
this.id = id;
|
||||||
|
this.parentId = parentId;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.author = author;
|
||||||
|
this.authorStatus = authorStatus;
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.read = read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageId getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author.Status getAuthorStatus() {
|
||||||
|
return authorStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRead() {
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageId getParentId() {
|
||||||
|
return parentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +1,17 @@
|
|||||||
package org.briarproject.api.forum;
|
package org.briarproject.api.forum;
|
||||||
|
|
||||||
import org.briarproject.api.clients.MessageTree;
|
import org.briarproject.api.clients.MessageTree;
|
||||||
|
import org.briarproject.api.clients.PostHeader;
|
||||||
import org.briarproject.api.identity.Author;
|
import org.briarproject.api.identity.Author;
|
||||||
import org.briarproject.api.sync.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
|
||||||
public class ForumPostHeader implements MessageTree.MessageNode {
|
public class ForumPostHeader extends PostHeader
|
||||||
|
implements MessageTree.MessageNode {
|
||||||
private final MessageId id;
|
|
||||||
private final MessageId parentId;
|
|
||||||
private final long timestamp;
|
|
||||||
private final Author author;
|
|
||||||
private final Author.Status authorStatus;
|
|
||||||
private final String contentType;
|
|
||||||
private final boolean read;
|
|
||||||
|
|
||||||
public ForumPostHeader(MessageId id, MessageId parentId, long timestamp,
|
public ForumPostHeader(MessageId id, MessageId parentId, long timestamp,
|
||||||
Author author, Author.Status authorStatus, String contentType,
|
Author author, Author.Status authorStatus, String contentType,
|
||||||
boolean read) {
|
boolean read) {
|
||||||
this.id = id;
|
super(id, parentId, timestamp, author, authorStatus, contentType, read);
|
||||||
this.parentId = parentId;
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.author = author;
|
|
||||||
this.authorStatus = authorStatus;
|
|
||||||
this.contentType = contentType;
|
|
||||||
this.read = read;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageId getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Author getAuthor() {
|
|
||||||
return author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Author.Status getAuthorStatus() {
|
|
||||||
return authorStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentType() {
|
|
||||||
return contentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRead() {
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageId getParentId() {
|
|
||||||
return parentId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.api.identity;
|
|||||||
|
|
||||||
import org.briarproject.api.db.DbException;
|
import org.briarproject.api.db.DbException;
|
||||||
import org.briarproject.api.db.Transaction;
|
import org.briarproject.api.db.Transaction;
|
||||||
|
import org.briarproject.api.identity.Author.Status;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@@ -25,6 +26,9 @@ public interface IdentityManager {
|
|||||||
/** Removes a local pseudonym and all associated state. */
|
/** Removes a local pseudonym and all associated state. */
|
||||||
void removeLocalAuthor(AuthorId a) throws DbException;
|
void removeLocalAuthor(AuthorId a) throws DbException;
|
||||||
|
|
||||||
|
/** Returns the trust-level status of the author */
|
||||||
|
Status getAuthorStatus(AuthorId a) throws DbException;
|
||||||
|
|
||||||
interface AddIdentityHook {
|
interface AddIdentityHook {
|
||||||
void addingIdentity(Transaction txn, LocalAuthor a) throws DbException;
|
void addingIdentity(Transaction txn, LocalAuthor a) throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject;
|
package org.briarproject;
|
||||||
|
|
||||||
|
import org.briarproject.blogs.BlogsModule;
|
||||||
import org.briarproject.contact.ContactModule;
|
import org.briarproject.contact.ContactModule;
|
||||||
import org.briarproject.crypto.CryptoModule;
|
import org.briarproject.crypto.CryptoModule;
|
||||||
import org.briarproject.db.DatabaseExecutorModule;
|
import org.briarproject.db.DatabaseExecutorModule;
|
||||||
@@ -15,6 +16,8 @@ import org.briarproject.transport.TransportModule;
|
|||||||
|
|
||||||
public interface CoreEagerSingletons {
|
public interface CoreEagerSingletons {
|
||||||
|
|
||||||
|
void inject(BlogsModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(ContactModule.EagerSingletons init);
|
void inject(ContactModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(CryptoModule.EagerSingletons init);
|
void inject(CryptoModule.EagerSingletons init);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject;
|
package org.briarproject;
|
||||||
|
|
||||||
|
import org.briarproject.blogs.BlogsModule;
|
||||||
import org.briarproject.clients.ClientsModule;
|
import org.briarproject.clients.ClientsModule;
|
||||||
import org.briarproject.contact.ContactModule;
|
import org.briarproject.contact.ContactModule;
|
||||||
import org.briarproject.crypto.CryptoModule;
|
import org.briarproject.crypto.CryptoModule;
|
||||||
@@ -26,6 +27,7 @@ import org.briarproject.transport.TransportModule;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
|
||||||
@Module(includes = {
|
@Module(includes = {
|
||||||
|
BlogsModule.class,
|
||||||
ClientsModule.class,
|
ClientsModule.class,
|
||||||
ContactModule.class,
|
ContactModule.class,
|
||||||
CryptoModule.class,
|
CryptoModule.class,
|
||||||
@@ -52,6 +54,7 @@ import dagger.Module;
|
|||||||
public class CoreModule {
|
public class CoreModule {
|
||||||
|
|
||||||
public static void initEagerSingletons(CoreEagerSingletons c) {
|
public static void initEagerSingletons(CoreEagerSingletons c) {
|
||||||
|
c.inject(new BlogsModule.EagerSingletons());
|
||||||
c.inject(new ContactModule.EagerSingletons());
|
c.inject(new ContactModule.EagerSingletons());
|
||||||
c.inject(new CryptoModule.EagerSingletons());
|
c.inject(new CryptoModule.EagerSingletons());
|
||||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||||
|
|||||||
61
briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package org.briarproject.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.blogs.Blog;
|
||||||
|
import org.briarproject.api.blogs.BlogFactory;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.data.BdfList;
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
|
import org.briarproject.api.sync.Group;
|
||||||
|
import org.briarproject.api.sync.GroupFactory;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
class BlogFactoryImpl implements BlogFactory {
|
||||||
|
|
||||||
|
private final GroupFactory groupFactory;
|
||||||
|
private final AuthorFactory authorFactory;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BlogFactoryImpl(GroupFactory groupFactory, AuthorFactory authorFactory,
|
||||||
|
ClientHelper clientHelper) {
|
||||||
|
|
||||||
|
this.groupFactory = groupFactory;
|
||||||
|
this.authorFactory = authorFactory;
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blog createBlog(@NotNull String name, @NotNull String description,
|
||||||
|
@NotNull Author author) {
|
||||||
|
try {
|
||||||
|
BdfList blog = BdfList.of(
|
||||||
|
name,
|
||||||
|
author.getName(),
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
byte[] descriptor = clientHelper.toByteArray(blog);
|
||||||
|
Group g = groupFactory
|
||||||
|
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
|
||||||
|
return new Blog(g, name, description, author);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blog parseBlog(@NotNull Group g, @NotNull String description)
|
||||||
|
throws FormatException {
|
||||||
|
|
||||||
|
byte[] descriptor = g.getDescriptor();
|
||||||
|
// Blog Name, Author Name, Public Key
|
||||||
|
BdfList blog = clientHelper.toList(descriptor, 0, descriptor.length);
|
||||||
|
Author a =
|
||||||
|
authorFactory.createAuthor(blog.getString(1), blog.getRaw(2));
|
||||||
|
return new Blog(g, blog.getString(0), description, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
260
briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
package org.briarproject.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.blogs.Blog;
|
||||||
|
import org.briarproject.api.blogs.BlogFactory;
|
||||||
|
import org.briarproject.api.blogs.BlogManager;
|
||||||
|
import org.briarproject.api.blogs.BlogPost;
|
||||||
|
import org.briarproject.api.blogs.BlogPostHeader;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.api.data.BdfEntry;
|
||||||
|
import org.briarproject.api.data.BdfList;
|
||||||
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.api.db.DbException;
|
||||||
|
import org.briarproject.api.db.Transaction;
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.identity.Author.Status;
|
||||||
|
import org.briarproject.api.identity.AuthorId;
|
||||||
|
import org.briarproject.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.api.sync.ClientId;
|
||||||
|
import org.briarproject.api.sync.Group;
|
||||||
|
import org.briarproject.api.sync.GroupId;
|
||||||
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
import org.briarproject.util.StringUtils;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_HAS_BODY;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_TEASER;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
|
||||||
|
|
||||||
|
class BlogManagerImpl implements BlogManager {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(BlogManagerImpl.class.getName());
|
||||||
|
|
||||||
|
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
|
||||||
|
"dafbe56f0c8971365cea4bb5f08ec9a6" +
|
||||||
|
"1d686e058b943997b6ff259ba423f613"));
|
||||||
|
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final IdentityManager identityManager;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
private final BlogFactory blogFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
|
||||||
|
ClientHelper clientHelper, BlogFactory blogFactory) {
|
||||||
|
|
||||||
|
this.db = db;
|
||||||
|
this.identityManager = identityManager;
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
this.blogFactory = blogFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientId getClientId() {
|
||||||
|
return CLIENT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blog addBlog(LocalAuthor localAuthor, String name,
|
||||||
|
String description) throws DbException {
|
||||||
|
|
||||||
|
Blog b = blogFactory
|
||||||
|
.createBlog(name, description, localAuthor);
|
||||||
|
BdfDictionary metadata = BdfDictionary.of(
|
||||||
|
new BdfEntry(KEY_DESCRIPTION, b.getDescription())
|
||||||
|
);
|
||||||
|
|
||||||
|
Transaction txn = db.startTransaction(false);
|
||||||
|
try {
|
||||||
|
db.addGroup(txn, b.getGroup());
|
||||||
|
clientHelper.mergeGroupMetadata(txn, b.getId(), metadata);
|
||||||
|
txn.setComplete();
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(txn);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLocalPost(BlogPost p) throws DbException {
|
||||||
|
try {
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle());
|
||||||
|
meta.put(KEY_TEASER, p.getTeaser());
|
||||||
|
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
|
||||||
|
meta.put(KEY_HAS_BODY, p.hasBody());
|
||||||
|
if (p.getParent() != null) meta.put(KEY_PARENT, p.getParent());
|
||||||
|
|
||||||
|
Author a = p.getAuthor();
|
||||||
|
BdfDictionary authorMeta = new BdfDictionary();
|
||||||
|
authorMeta.put(KEY_AUTHOR_ID, a.getId());
|
||||||
|
authorMeta.put(KEY_AUTHOR_NAME, a.getName());
|
||||||
|
authorMeta.put(KEY_PUBLIC_KEY, a.getPublicKey());
|
||||||
|
meta.put(KEY_AUTHOR, authorMeta);
|
||||||
|
|
||||||
|
meta.put(KEY_CONTENT_TYPE, p.getContentType());
|
||||||
|
meta.put(KEY_READ, true);
|
||||||
|
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blog getBlog(GroupId g) throws DbException {
|
||||||
|
Blog blog;
|
||||||
|
Transaction txn = db.startTransaction(true);
|
||||||
|
try {
|
||||||
|
blog = getBlog(txn, g);
|
||||||
|
txn.setComplete();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(txn);
|
||||||
|
}
|
||||||
|
return blog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blog getBlog(Transaction txn, GroupId g) throws DbException {
|
||||||
|
try {
|
||||||
|
Group group = db.getGroup(txn, g);
|
||||||
|
String description = getBlogDescription(txn, g);
|
||||||
|
return blogFactory.parseBlog(group, description);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Blog> getBlogs(LocalAuthor localAuthor)
|
||||||
|
throws DbException {
|
||||||
|
|
||||||
|
Collection<Blog> allBlogs = getBlogs();
|
||||||
|
List<Blog> blogs = new ArrayList<Blog>();
|
||||||
|
for (Blog b : allBlogs) {
|
||||||
|
if (b.getAuthor().equals(localAuthor)) {
|
||||||
|
blogs.add(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(blogs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Blog> getBlogs() throws DbException {
|
||||||
|
try {
|
||||||
|
List<Blog> blogs = new ArrayList<Blog>();
|
||||||
|
Collection<Group> groups;
|
||||||
|
Transaction txn = db.startTransaction(true);
|
||||||
|
try {
|
||||||
|
groups = db.getGroups(txn, CLIENT_ID);
|
||||||
|
for (Group g : groups) {
|
||||||
|
String description = getBlogDescription(txn, g.getId());
|
||||||
|
blogs.add(blogFactory.parseBlog(g, description));
|
||||||
|
}
|
||||||
|
txn.setComplete();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(txn);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(blogs);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public byte[] getPostBody(MessageId m) throws DbException {
|
||||||
|
try {
|
||||||
|
// content, signature
|
||||||
|
// content: parent, contentType, title, teaser, body, attachments
|
||||||
|
BdfList message = clientHelper.getMessageAsList(m);
|
||||||
|
BdfList content = message.getList(0);
|
||||||
|
return content.getRaw(4);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<BlogPostHeader> getPostHeaders(GroupId g)
|
||||||
|
throws DbException {
|
||||||
|
|
||||||
|
Map<MessageId, BdfDictionary> metadata;
|
||||||
|
try {
|
||||||
|
metadata = clientHelper.getMessageMetadataAsDictionary(g);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
Collection<BlogPostHeader> headers = new ArrayList<BlogPostHeader>();
|
||||||
|
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||||
|
try {
|
||||||
|
BdfDictionary meta = entry.getValue();
|
||||||
|
String title = meta.getOptionalString(KEY_TITLE);
|
||||||
|
String teaser = meta.getString(KEY_TEASER);
|
||||||
|
boolean hasBody = meta.getBoolean(KEY_HAS_BODY);
|
||||||
|
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
||||||
|
MessageId parentId = null;
|
||||||
|
if (meta.containsKey(KEY_PARENT))
|
||||||
|
parentId = new MessageId(meta.getRaw(KEY_PARENT));
|
||||||
|
|
||||||
|
BdfDictionary d = meta.getDictionary(KEY_AUTHOR);
|
||||||
|
AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID));
|
||||||
|
String name = d.getString(KEY_AUTHOR_NAME);
|
||||||
|
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
|
||||||
|
Author author = new Author(authorId, name, publicKey);
|
||||||
|
Status authorStatus = identityManager.getAuthorStatus(authorId);
|
||||||
|
|
||||||
|
String contentType = meta.getString(KEY_CONTENT_TYPE);
|
||||||
|
boolean read = meta.getBoolean(KEY_READ);
|
||||||
|
headers.add(new BlogPostHeader(title, teaser, hasBody,
|
||||||
|
entry.getKey(), parentId, timestamp, author,
|
||||||
|
authorStatus, contentType, read));
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
||||||
|
try {
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put(KEY_READ, read);
|
||||||
|
clientHelper.mergeMessageMetadata(m, meta);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBlogDescription(Transaction txn, GroupId g)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||||
|
return d.getString(KEY_DESCRIPTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package org.briarproject.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.blogs.BlogPost;
|
||||||
|
import org.briarproject.api.blogs.BlogPostFactory;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.api.crypto.KeyParser;
|
||||||
|
import org.briarproject.api.crypto.PrivateKey;
|
||||||
|
import org.briarproject.api.crypto.Signature;
|
||||||
|
import org.briarproject.api.data.BdfList;
|
||||||
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.api.sync.GroupId;
|
||||||
|
import org.briarproject.api.sync.Message;
|
||||||
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
import org.briarproject.util.StringUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TEASER_LENGTH;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||||
|
|
||||||
|
class BlogPostFactoryImpl implements BlogPostFactory {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BlogPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlogPost createBlogPost(@NotNull GroupId groupId,
|
||||||
|
@Nullable String title, @NotNull String teaser, long timestamp,
|
||||||
|
@Nullable MessageId parent, @NotNull LocalAuthor author,
|
||||||
|
@NotNull String contentType, @Nullable byte[] body)
|
||||||
|
throws FormatException, GeneralSecurityException {
|
||||||
|
|
||||||
|
// Validate the arguments
|
||||||
|
if (title != null &&
|
||||||
|
StringUtils.toUtf8(title).length > MAX_BLOG_POST_TITLE_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (StringUtils.toUtf8(teaser).length > MAX_BLOG_POST_TEASER_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (body != null && body.length > MAX_BLOG_POST_BODY_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
|
||||||
|
// Serialise the data to be signed
|
||||||
|
BdfList content =
|
||||||
|
BdfList.of(parent, contentType, title, teaser, body, null);
|
||||||
|
BdfList signed = BdfList.of(groupId, timestamp, content);
|
||||||
|
|
||||||
|
// Generate the signature
|
||||||
|
Signature signature = crypto.getSignature();
|
||||||
|
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||||
|
PrivateKey privateKey =
|
||||||
|
keyParser.parsePrivateKey(author.getPrivateKey());
|
||||||
|
signature.initSign(privateKey);
|
||||||
|
signature.update(clientHelper.toByteArray(signed));
|
||||||
|
byte[] sig = signature.sign();
|
||||||
|
|
||||||
|
// Serialise the signed message
|
||||||
|
BdfList message = BdfList.of(content, sig);
|
||||||
|
Message m = clientHelper.createMessage(groupId, timestamp, message);
|
||||||
|
return new BlogPost(title, teaser, body != null, m, parent, author,
|
||||||
|
contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
125
briar-core/src/org/briarproject/blogs/BlogPostValidator.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package org.briarproject.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.UniqueId;
|
||||||
|
import org.briarproject.api.blogs.Blog;
|
||||||
|
import org.briarproject.api.blogs.BlogFactory;
|
||||||
|
import org.briarproject.api.clients.BdfMessageContext;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.api.crypto.KeyParser;
|
||||||
|
import org.briarproject.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.api.crypto.Signature;
|
||||||
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.api.data.BdfList;
|
||||||
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.sync.Group;
|
||||||
|
import org.briarproject.api.sync.InvalidMessageException;
|
||||||
|
import org.briarproject.api.sync.Message;
|
||||||
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.clients.BdfMessageValidator;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_HAS_BODY;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_TEASER;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TEASER_LENGTH;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
|
||||||
|
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||||
|
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
|
|
||||||
|
class BlogPostValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final BlogFactory blogFactory;
|
||||||
|
|
||||||
|
BlogPostValidator(CryptoComponent crypto, BlogFactory blogFactory,
|
||||||
|
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||||
|
Clock clock) {
|
||||||
|
super(clientHelper, metadataEncoder, clock);
|
||||||
|
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.blogFactory = blogFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||||
|
BdfList body) throws InvalidMessageException, FormatException {
|
||||||
|
|
||||||
|
// Content, Signature
|
||||||
|
checkSize(body, 2);
|
||||||
|
BdfList content = body.getList(0);
|
||||||
|
|
||||||
|
// Content: Parent ID, content type, title (optional), teaser,
|
||||||
|
// post body (optional), attachments (optional)
|
||||||
|
checkSize(body, 6);
|
||||||
|
// Parent ID is optional
|
||||||
|
byte[] parent = content.getOptionalRaw(0);
|
||||||
|
checkLength(parent, UniqueId.LENGTH);
|
||||||
|
// Content type
|
||||||
|
String contentType = content.getString(1);
|
||||||
|
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
|
||||||
|
// Blog post title is optional
|
||||||
|
String title = content.getOptionalString(2);
|
||||||
|
checkLength(contentType, 0, MAX_BLOG_POST_TITLE_LENGTH);
|
||||||
|
// Blog teaser
|
||||||
|
String teaser = content.getString(3);
|
||||||
|
// TODO make sure that there is only text in the teaser
|
||||||
|
checkLength(contentType, 0, MAX_BLOG_POST_TEASER_LENGTH);
|
||||||
|
// Blog post body is optional
|
||||||
|
byte[] postBody = content.getOptionalRaw(4);
|
||||||
|
checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH);
|
||||||
|
// Attachments
|
||||||
|
BdfDictionary attachments = content.getOptionalDictionary(5);
|
||||||
|
// TODO handle attachments somehow
|
||||||
|
|
||||||
|
// Signature
|
||||||
|
byte[] sig = body.getRaw(1);
|
||||||
|
checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
|
||||||
|
// Verify the signature
|
||||||
|
try {
|
||||||
|
// Get the blog author
|
||||||
|
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
|
||||||
|
Author a = b.getAuthor();
|
||||||
|
// Parse the public key
|
||||||
|
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||||
|
PublicKey key = keyParser.parsePublicKey(a.getPublicKey());
|
||||||
|
// Serialise the data to be signed
|
||||||
|
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), content);
|
||||||
|
// Verify the signature
|
||||||
|
Signature signature = crypto.getSignature();
|
||||||
|
signature.initVerify(key);
|
||||||
|
signature.update(clientHelper.toByteArray(signed));
|
||||||
|
if (!signature.verify(sig)) {
|
||||||
|
throw new InvalidMessageException("Invalid signature");
|
||||||
|
}
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new InvalidMessageException("Invalid public key");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the metadata and dependencies
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
Collection<MessageId> dependencies = null;
|
||||||
|
if (title != null) meta.put(KEY_TITLE, title);
|
||||||
|
meta.put(KEY_TEASER, teaser);
|
||||||
|
meta.put(KEY_HAS_BODY, postBody != null);
|
||||||
|
meta.put(KEY_TIMESTAMP, m.getTimestamp());
|
||||||
|
if (parent != null) {
|
||||||
|
meta.put(KEY_PARENT, parent);
|
||||||
|
dependencies = Collections.singletonList(new MessageId(parent));
|
||||||
|
}
|
||||||
|
meta.put(KEY_CONTENT_TYPE, contentType);
|
||||||
|
meta.put(KEY_READ, false);
|
||||||
|
return new BdfMessageContext(meta, dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
briar-core/src/org/briarproject/blogs/BlogsModule.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package org.briarproject.blogs;
|
||||||
|
|
||||||
|
import org.briarproject.api.blogs.BlogFactory;
|
||||||
|
import org.briarproject.api.blogs.BlogManager;
|
||||||
|
import org.briarproject.api.blogs.BlogPostFactory;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
|
import org.briarproject.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.api.sync.GroupFactory;
|
||||||
|
import org.briarproject.api.sync.ValidationManager;
|
||||||
|
import org.briarproject.api.system.Clock;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class BlogsModule {
|
||||||
|
|
||||||
|
public static class EagerSingletons {
|
||||||
|
@Inject
|
||||||
|
BlogPostValidator blogPostValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
BlogManager provideBlogManager(BlogManagerImpl blogManager) {
|
||||||
|
return blogManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
BlogPostFactory provideBlogPostFactory(CryptoComponent crypto,
|
||||||
|
ClientHelper clientHelper) {
|
||||||
|
return new BlogPostFactoryImpl(crypto, clientHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
BlogFactory provideBlogFactory(GroupFactory groupFactory,
|
||||||
|
AuthorFactory authorFactory, ClientHelper clientHelper) {
|
||||||
|
return new BlogFactoryImpl(groupFactory, authorFactory, clientHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
BlogPostValidator provideBlogPostValidator(
|
||||||
|
ValidationManager validationManager, CryptoComponent crypto,
|
||||||
|
BlogFactory blogFactory, ClientHelper clientHelper,
|
||||||
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
|
|
||||||
|
BlogPostValidator validator = new BlogPostValidator(crypto,
|
||||||
|
blogFactory, clientHelper, metadataEncoder, clock);
|
||||||
|
validationManager.registerMessageValidator(
|
||||||
|
BlogManagerImpl.CLIENT_ID, validator);
|
||||||
|
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package org.briarproject.forum;
|
|||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.clients.ClientHelper;
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.contact.Contact;
|
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfList;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
@@ -14,8 +13,9 @@ import org.briarproject.api.forum.ForumManager;
|
|||||||
import org.briarproject.api.forum.ForumPost;
|
import org.briarproject.api.forum.ForumPost;
|
||||||
import org.briarproject.api.forum.ForumPostHeader;
|
import org.briarproject.api.forum.ForumPostHeader;
|
||||||
import org.briarproject.api.identity.Author;
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.identity.Author.Status;
|
||||||
import org.briarproject.api.identity.AuthorId;
|
import org.briarproject.api.identity.AuthorId;
|
||||||
import org.briarproject.api.identity.LocalAuthor;
|
import org.briarproject.api.identity.IdentityManager;
|
||||||
import org.briarproject.api.sync.ClientId;
|
import org.briarproject.api.sync.ClientId;
|
||||||
import org.briarproject.api.sync.Group;
|
import org.briarproject.api.sync.Group;
|
||||||
import org.briarproject.api.sync.GroupId;
|
import org.briarproject.api.sync.GroupId;
|
||||||
@@ -25,11 +25,9 @@ import org.briarproject.util.StringUtils;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -45,8 +43,6 @@ import static org.briarproject.api.forum.ForumConstants.KEY_PUBLIC_NAME;
|
|||||||
import static org.briarproject.api.forum.ForumConstants.KEY_READ;
|
import static org.briarproject.api.forum.ForumConstants.KEY_READ;
|
||||||
import static org.briarproject.api.forum.ForumConstants.KEY_TIMESTAMP;
|
import static org.briarproject.api.forum.ForumConstants.KEY_TIMESTAMP;
|
||||||
import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
|
import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
|
||||||
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
|
|
||||||
import static org.briarproject.api.identity.Author.Status.VERIFIED;
|
|
||||||
|
|
||||||
class ForumManagerImpl implements ForumManager {
|
class ForumManagerImpl implements ForumManager {
|
||||||
|
|
||||||
@@ -58,15 +54,17 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
+ "795af837abbf8c16d750b3c2ccc186ea"));
|
+ "795af837abbf8c16d750b3c2ccc186ea"));
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
|
private final IdentityManager identityManager;
|
||||||
private final ClientHelper clientHelper;
|
private final ClientHelper clientHelper;
|
||||||
private final ForumFactory forumFactory;
|
private final ForumFactory forumFactory;
|
||||||
private final List<RemoveForumHook> removeHooks;
|
private final List<RemoveForumHook> removeHooks;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
ForumManagerImpl(DatabaseComponent db, IdentityManager identityManager,
|
||||||
ForumFactory forumFactory) {
|
ClientHelper clientHelper, ForumFactory forumFactory) {
|
||||||
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.identityManager = identityManager;
|
||||||
this.clientHelper = clientHelper;
|
this.clientHelper = clientHelper;
|
||||||
this.forumFactory = forumFactory;
|
this.forumFactory = forumFactory;
|
||||||
removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
|
removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
|
||||||
@@ -183,24 +181,12 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
|
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
|
|
||||||
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
|
|
||||||
Map<MessageId, BdfDictionary> metadata;
|
Map<MessageId, BdfDictionary> metadata;
|
||||||
Transaction txn = db.startTransaction(true);
|
|
||||||
try {
|
try {
|
||||||
// Load the IDs of the user's identities
|
metadata = clientHelper.getMessageMetadataAsDictionary(g);
|
||||||
for (LocalAuthor a : db.getLocalAuthors(txn))
|
|
||||||
localAuthorIds.add(a.getId());
|
|
||||||
// Load the IDs of contacts' identities
|
|
||||||
for (Contact c : db.getContacts(txn))
|
|
||||||
contactAuthorIds.add(c.getAuthor().getId());
|
|
||||||
// Load the metadata
|
|
||||||
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
|
|
||||||
txn.setComplete();
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
}
|
||||||
// Parse the metadata
|
// Parse the metadata
|
||||||
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
|
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
|
||||||
@@ -209,7 +195,7 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
BdfDictionary meta = entry.getValue();
|
BdfDictionary meta = entry.getValue();
|
||||||
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
||||||
Author author = null;
|
Author author = null;
|
||||||
Author.Status authorStatus = ANONYMOUS;
|
Status authorStatus = ANONYMOUS;
|
||||||
MessageId parentId = null;
|
MessageId parentId = null;
|
||||||
if (meta.containsKey(KEY_PARENT))
|
if (meta.containsKey(KEY_PARENT))
|
||||||
parentId = new MessageId(meta.getRaw(KEY_PARENT));
|
parentId = new MessageId(meta.getRaw(KEY_PARENT));
|
||||||
@@ -219,11 +205,8 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
String name = d1.getString(KEY_NAME);
|
String name = d1.getString(KEY_NAME);
|
||||||
byte[] publicKey = d1.getRaw(KEY_PUBLIC_NAME);
|
byte[] publicKey = d1.getRaw(KEY_PUBLIC_NAME);
|
||||||
author = new Author(authorId, name, publicKey);
|
author = new Author(authorId, name, publicKey);
|
||||||
if (localAuthorIds.contains(authorId))
|
authorStatus =
|
||||||
authorStatus = VERIFIED;
|
identityManager.getAuthorStatus(author.getId());
|
||||||
else if (contactAuthorIds.contains(authorId))
|
|
||||||
authorStatus = VERIFIED;
|
|
||||||
else authorStatus = UNKNOWN;
|
|
||||||
}
|
}
|
||||||
String contentType = meta.getString(KEY_CONTENT_TYPE);
|
String contentType = meta.getString(KEY_CONTENT_TYPE);
|
||||||
boolean read = meta.getBoolean(KEY_READ);
|
boolean read = meta.getBoolean(KEY_READ);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.api.forum.ForumManager;
|
|||||||
import org.briarproject.api.forum.ForumPostFactory;
|
import org.briarproject.api.forum.ForumPostFactory;
|
||||||
import org.briarproject.api.forum.ForumSharingManager;
|
import org.briarproject.api.forum.ForumSharingManager;
|
||||||
import org.briarproject.api.identity.AuthorFactory;
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
|
import org.briarproject.api.identity.IdentityManager;
|
||||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.api.sync.GroupFactory;
|
import org.briarproject.api.sync.GroupFactory;
|
||||||
import org.briarproject.api.sync.ValidationManager;
|
import org.briarproject.api.sync.ValidationManager;
|
||||||
@@ -38,9 +39,8 @@ public class ForumModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
ForumManager provideForumManager(DatabaseComponent db,
|
ForumManager provideForumManager(ForumManagerImpl forumManager) {
|
||||||
ClientHelper clientHelper, ForumFactory forumFactory) {
|
return forumManager;
|
||||||
return new ForumManagerImpl(db, clientHelper, forumFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -88,7 +88,7 @@ public class ForumModule {
|
|||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager,
|
||||||
ContactManager contactManager,
|
ContactManager contactManager,
|
||||||
MessageQueueManager messageQueueManager,
|
MessageQueueManager messageQueueManager,
|
||||||
ForumManager forumManager, ForumFactory forumFactory,
|
ForumManager forumManager,
|
||||||
ForumSharingManagerImpl forumSharingManager) {
|
ForumSharingManagerImpl forumSharingManager) {
|
||||||
|
|
||||||
lifecycleManager.registerClient(forumSharingManager);
|
lifecycleManager.registerClient(forumSharingManager);
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
package org.briarproject.identity;
|
package org.briarproject.identity;
|
||||||
|
|
||||||
|
import org.briarproject.api.contact.Contact;
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
import org.briarproject.api.db.DbException;
|
import org.briarproject.api.db.DbException;
|
||||||
import org.briarproject.api.db.Transaction;
|
import org.briarproject.api.db.Transaction;
|
||||||
|
import org.briarproject.api.identity.Author.Status;
|
||||||
import org.briarproject.api.identity.AuthorId;
|
import org.briarproject.api.identity.AuthorId;
|
||||||
import org.briarproject.api.identity.IdentityManager;
|
import org.briarproject.api.identity.IdentityManager;
|
||||||
import org.briarproject.api.identity.LocalAuthor;
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
|
||||||
|
import static org.briarproject.api.identity.Author.Status.VERIFIED;
|
||||||
|
|
||||||
class IdentityManagerImpl implements IdentityManager {
|
class IdentityManagerImpl implements IdentityManager {
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final List<AddIdentityHook> addHooks;
|
private final List<AddIdentityHook> addHooks;
|
||||||
@@ -87,4 +94,22 @@ class IdentityManagerImpl implements IdentityManager {
|
|||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Status getAuthorStatus(AuthorId authorId) throws DbException {
|
||||||
|
Transaction txn = db.startTransaction(false);
|
||||||
|
try {
|
||||||
|
// Compare to the IDs of the user's identities
|
||||||
|
for (LocalAuthor a : db.getLocalAuthors(txn))
|
||||||
|
if (a.getId().equals(authorId)) return VERIFIED;
|
||||||
|
// Compare to the IDs of contacts' identities
|
||||||
|
for (Contact c : db.getContacts(txn))
|
||||||
|
if (c.getAuthor().getId().equals(authorId)) return VERIFIED;
|
||||||
|
|
||||||
|
// TODO also handle UNVERIFIED when #261 is implemented
|
||||||
|
return UNKNOWN;
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(txn);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||