PageRenderTime 62ms CodeModel.GetById 9ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 1ms

/src/support/z_datamodel.erl

https://code.google.com/p/zotonic/
Erlang | 263 lines | 192 code | 38 blank | 33 comment | 5 complexity | aee96d75aeb76ac612a515dd25346e55 MD5 | raw file
  1%% @author Arjan Scherpenisse <arjan@scherpenisse.net>
  2%% @copyright 2009 Arjan Scherpenisse
  3%% Date: 2009-11-08
  4%% @doc Installing parts of the zotonic datamodel. Installs
  5%% predicates, categories and default resources.
  6
  7%% Copyright 2009 Arjan Scherpenisse
  8%%
  9%% Licensed under the Apache License, Version 2.0 (the "License");
 10%% you may not use this file except in compliance with the License.
 11%% You may obtain a copy of the License at
 12%%
 13%%     http://www.apache.org/licenses/LICENSE-2.0
 14%%
 15%% Unless required by applicable law or agreed to in writing, software
 16%% distributed under the License is distributed on an "AS IS" BASIS,
 17%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 18%% See the License for the specific language governing permissions and
 19%% limitations under the License.
 20
 21-module(z_datamodel).
 22-author("Arjan Scherpenisse <arjan@scherpenisse.net>").
 23
 24-export([manage/3, reset_deleted/2]).
 25
 26
 27%% The datamodel manages parts of your datamodel. This includes
 28%% categories and predicates, but also "default data" like resources.
 29%%
 30%% The data model maintains a special property on this installed data,
 31%% called 'installed_by'. This decides whether it can touch it.
 32%%
 33
 34-include_lib("zotonic.hrl").
 35
 36
 37%% @doc Reset the state of an imported datamodel, causing all deleted resources to be reimported
 38reset_deleted(Module, Context) ->
 39    m_config:delete(Module, datamodel, Context).
 40
 41
 42
 43manage(Module, Datamodel, Context) when is_list(Datamodel) ->
 44    %% Backwards compatibility with old datamodel notation.
 45    manage(Module,
 46           #datamodel{categories=proplists:get_value(categories, Datamodel, []),
 47                      predicates=proplists:get_value(predicates, Datamodel, []),
 48                      resources=proplists:get_value(resources, Datamodel, []),
 49                      media=proplists:get_value(media, Datamodel, []),
 50                      edges=proplists:get_value(edges, Datamodel, [])
 51                     },
 52           Context);
 53
 54manage(Module, Datamodel, Context) ->
 55    AdminContext = z_acl:sudo(Context),
 56    [manage_category(Module, Cat, AdminContext) || Cat <- Datamodel#datamodel.categories],
 57    [manage_predicate(Module, Pred, AdminContext) || Pred <- Datamodel#datamodel.predicates],
 58    [manage_resource(Module, R, AdminContext) || R <- Datamodel#datamodel.resources],
 59    [manage_medium(Module, Medium, AdminContext) || Medium <- Datamodel#datamodel.media],
 60    [manage_edge(Module, Edge, AdminContext) || Edge <- Datamodel#datamodel.edges],
 61    ok.
 62
 63
 64manage_medium(Module, {Name, {EmbedService, EmbedCode}, Props}, Context) ->
 65    case manage_resource(Module, {Name, media, Props}, Context) of
 66        {ok} ->
 67            {ok};
 68        {ok, Id} ->
 69            MediaProps = [{mime, "text/html-video-embed"}, 
 70                          {video_embed_service, EmbedService}, 
 71                          {video_embed_code, EmbedCode}
 72                         ],
 73            m_media:replace(Id, MediaProps, Context),
 74            {ok, Id}
 75    end;
 76
 77manage_medium(Module, {Name, Filename, Props}, Context) ->
 78    case manage_resource(Module, {Name, media, Props}, Context) of
 79        {ok} ->
 80            {ok};
 81        {ok, Id} ->
 82            m_media:replace_file(Filename, Id, Context),
 83            {ok, Id}
 84    end.
 85
 86
 87manage_category(Module, {Name, ParentCategory, Props}, Context) ->
 88    case manage_resource(Module, {Name, category, Props}, Context) of
 89        {ok} ->
 90            {ok};
 91        {ok, Id} ->
 92            case ParentCategory of
 93                undefined ->
 94                    {ok, Id};
 95                _ ->
 96                    case m_category:name_to_id(ParentCategory, Context) of
 97                        {ok, PId} ->
 98                            m_category:move_below(Id, PId, Context),
 99                            {ok, Id};
100                        _ -> throw({error, {nonexisting_parent_category, ParentCategory}})
101                    end
102            end
103    end.
104
105
106manage_predicate(Module, {Name, Uri, Props, ValidFor}, Context) ->
107    manage_predicate(Module, {Name, [{uri,Uri}|Props], ValidFor}, Context);
108
109manage_predicate(Module, {Name, Props, ValidFor}, Context) ->
110    case manage_resource(Module, {Name, predicate, Props}, Context) of
111        {ok} ->
112            {ok};
113        {ok, Id} ->
114            ok = manage_predicate_validfor(Id, ValidFor, Context),
115            {ok, Id}
116    end.
117
118
119manage_resource(Module, {Name, Category, Props0}, Context) ->
120    case m_category:name_to_id(Category, Context) of
121        {ok, CatId} -> 
122            Props = map_props(Props0, Context),
123            case m_rsc:name_to_id(Name, Context) of
124                {ok, Id} ->
125                    case m_rsc:p(Id, installed_by, Context) of
126                        Module ->
127                            NewProps = update_new_props(Module, Id, Props, Context),
128                            m_rsc:update(Id, [{managed_props, z_html:escape_props(Props)} | NewProps], Context),
129                            {ok};
130                        _ ->
131                            %% Resource exists but is not installed by us.
132                            ?zInfo(io_lib:format("Resource '~p' (~p) exists but is not managed by ~p.", [Name, Id, Module]), Context),
133                            {ok}
134                    end;
135                {error, {unknown_rsc, _}} ->
136                    %% new resource, or old resource
137                    Props1 = [{name, Name}, {category_id, CatId},
138                              {is_protected, true},
139                              {installed_by, Module}, {managed_props, z_html:escape_props(Props)}] ++ Props,
140                    Props2 = case proplists:get_value(is_published, Props1) of
141                                 undefined -> [{is_published, true} | Props1];
142                                 _ -> Props1
143                             end,
144                    Props3 = case proplists:get_value(visible_for, Props2) of
145                                 undefined -> [{visible_for, ?ACL_VIS_PUBLIC} | Props2];
146                                 _ -> Props2
147                             end,
148                    ?zInfo(io_lib:format("Creating new resource '~p'", [Name]), Context),
149                    {ok, Id} = m_rsc:insert(Props3, Context),
150                    case proplists:get_value(media_url, Props3) of
151                        undefined ->
152                            nop;
153                        Url ->
154                            m_media:replace_url(Url, Id, [], Context)
155                    end,
156                    case proplists:get_value(media_file, Props3) of
157                        undefined ->
158                            nop;
159                        File ->
160                            m_media:replace_file(File, Id, Context)
161                    end,
162                    {ok, Id}
163            end;
164        {error, _} ->
165            Msg = io_lib:format("Resource '~p' could not be handled because the category ~p does not exist.", [Name, Category]),
166            ?zWarning(Msg, Context),
167            {ok}
168    end.
169
170update_new_props(Module, Id, NewProps, Context) ->
171    case m_rsc:p(Id, managed_props, Context) of
172        undefined ->
173            NewProps;
174        PreviousProps ->
175            lists:foldl(fun({K, V}, Props) ->
176                                case m_rsc:p(Id, K, Context) of
177                                    V ->
178                                        %% New value == current value
179                                        Props;
180                                    DbVal ->
181                                        case proplists:get_value(K, PreviousProps) of 
182                                            DbVal ->
183                                                %% New value in NewProps, unchanged in DB
184                                                [{K,V} | Props];
185                                            _PrevVal when is_binary(DbVal) ->
186                                                %% Compare with converted to list value
187                                                case z_convert:to_list(DbVal) of
188                                                    V ->
189                                                        Props;
190                                                    _ ->
191                                                        %% Changed by someone else
192                                                        ?zInfo(io_lib:format("~p: ~p of ~p changed in database, not updating.", [Module, K, Id]), Context),
193                                                        Props
194                                                end;
195                                            _PrevVal2 ->
196
197                                                %% Changed by someone else
198                                                ?zInfo(io_lib:format("~p: ~p of ~p changed in database, not updating.", [Module, K, Id]), Context),
199                                                Props
200                                        end
201                                end
202                        end, [], NewProps)
203    end.
204
205
206manage_predicate_validfor(_Id, [], _Context) ->
207    ok;
208manage_predicate_validfor(Id, [{SubjectCat, ObjectCat} | Rest], Context) ->
209    SubjectId = m_rsc:name_to_id_check(SubjectCat, Context),
210    ObjectId  = m_rsc:name_to_id_check(ObjectCat, Context),
211
212    F = fun(_S, _I, undefined) ->
213                ok;
214           (S, I, C) ->
215                case z_db:q("SELECT 1 FROM predicate_category WHERE predicate_id = $1 AND is_subject = $2 AND category_id = $3", [S, I, C], Context) of
216                    [{1}] ->
217                        ok;
218                    _ ->
219                        z_db:q("insert into predicate_category (predicate_id, is_subject, category_id) values ($1, $2, $3)", [S, I, C], Context),
220                        ok
221                end
222        end,
223    F(Id, true, SubjectId),
224    F(Id, false, ObjectId),
225
226    manage_predicate_validfor(Id, Rest, Context).
227
228
229
230map_props(Props, Context) ->
231    map_props(Props, Context, []).
232
233map_props([], _Context, Acc) ->
234    Acc;
235map_props([{Key, Value}|Rest], Context, Acc) ->
236    Value2 = map_prop(Value, Context),
237    map_props(Rest, Context, [{Key, Value2}|Acc]).
238
239
240map_prop({file, Filepath}, _Context) ->
241    {ok, Txt} = file:read_file(Filepath),
242    Txt;
243map_prop({to_id, Name}, Context) ->
244    case m_rsc:name_to_id(Name, Context) of
245             {ok, Id} -> Id;
246             _ -> undefined
247    end;
248map_prop(Value, _Context) ->
249    Value.
250
251
252manage_edge(_Module, {SubjectName, PredicateName, ObjectName}, Context) ->
253    Subject = m_rsc:name_to_id(SubjectName, Context),
254    Predicate = m_predicate:name_to_id(PredicateName, Context),
255    Object = m_rsc:name_to_id(ObjectName, Context),
256    case {Subject, Predicate, Object} of
257        {{ok, SubjectId}, {ok, PredicateId}, {ok,ObjectId}} ->
258            m_edge:insert(SubjectId, PredicateId, ObjectId, Context);
259        _ ->
260            skip %% One part of the triple was MIA
261    end.
262
263