1#include "DMWriteTask.h"
2
3#include "DMUtil.h"
4#include "SkColorPriv.h"
5#include "SkCommonFlags.h"
6#include "SkData.h"
7#include "SkImageEncoder.h"
8#include "SkMD5.h"
9#include "SkMallocPixelRef.h"
10#include "SkOSFile.h"
11#include "SkStream.h"
12#include "SkString.h"
13
14DEFINE_bool(nameByHash, false, "If true, write .../hash.png instead of .../mode/config/name.png");
15
16namespace DM {
17
18// Splits off the last N suffixes of name (splitting on _) and appends them to out.
19// Returns the total number of characters consumed.
20static int split_suffixes(int N, const char* name, SkTArray<SkString>* out) {
21    SkTArray<SkString> split;
22    SkStrSplit(name, "_", &split);
23    int consumed = 0;
24    for (int i = 0; i < N; i++) {
25        // We're splitting off suffixes from the back to front.
26        out->push_back(split[split.count()-i-1]);
27        consumed += out->back().size() + 1;  // Add one for the _.
28    }
29    return consumed;
30}
31
32inline static SkString find_base_name(const Task& parent, SkTArray<SkString>* suffixList) {
33    const int suffixes = parent.depth() + 1;
34    const SkString& name = parent.name();
35    const int totalSuffixLength = split_suffixes(suffixes, name.c_str(), suffixList);
36    return SkString(name.c_str(), name.size() - totalSuffixLength);
37}
38
39WriteTask::WriteTask(const Task& parent, const char* sourceType, SkBitmap bitmap)
40    : CpuTask(parent)
41    , fBaseName(find_base_name(parent, &fSuffixes))
42    , fSourceType(sourceType)
43    , fBitmap(bitmap)
44    , fData(NULL)
45    , fExtension(".png") {
46}
47
48WriteTask::WriteTask(const Task& parent,
49                     const char* sourceType,
50                     SkStreamAsset *data,
51                     const char* ext)
52    : CpuTask(parent)
53    , fBaseName(find_base_name(parent, &fSuffixes))
54    , fSourceType(sourceType)
55    , fData(data)
56    , fExtension(ext) {
57    SkASSERT(fData.get());
58    SkASSERT(fData->unique());
59}
60
61void WriteTask::makeDirOrFail(SkString dir) {
62    if (!sk_mkdir(dir.c_str())) {
63        this->fail();
64    }
65}
66
67static SkString get_md5(const void* ptr, size_t len) {
68    SkMD5 hasher;
69    hasher.write(ptr, len);
70    SkMD5::Digest digest;
71    hasher.finish(digest);
72
73    SkString md5;
74    for (int i = 0; i < 16; i++) {
75        md5.appendf("%02x", digest.data[i]);
76    }
77    return md5;
78}
79
80struct JsonData {
81    SkString name;            // E.g. "ninepatch-stretch", "desk-gws_skp"
82    SkString config;          //      "gpu", "8888"
83    SkString mode;            //      "direct", "default-tilegrid", "pipe"
84    SkString sourceType;      //      "GM", "SKP"
85    SkString md5;             // In ASCII, so 32 bytes long.
86};
87SkTArray<JsonData> gJsonData;
88SK_DECLARE_STATIC_MUTEX(gJsonDataLock);
89
90void WriteTask::draw() {
91    SkString md5;
92    {
93        SkAutoLockPixels lock(fBitmap);
94        md5 = fData ? get_md5(fData->getMemoryBase(), fData->getLength())
95                    : get_md5(fBitmap.getPixels(), fBitmap.getSize());
96    }
97
98    SkASSERT(fSuffixes.count() > 0);
99    SkString config = fSuffixes.back();
100    SkString mode("direct");
101    if (fSuffixes.count() > 1) {
102        mode = fSuffixes.fromBack(1);
103    }
104
105    JsonData entry = { fBaseName, config, mode, fSourceType, md5 };
106    {
107        SkAutoMutexAcquire lock(&gJsonDataLock);
108        gJsonData.push_back(entry);
109    }
110
111    SkString dir(FLAGS_writePath[0]);
112#if SK_BUILD_FOR_IOS
113    if (dir.equals("@")) {
114        dir.set(FLAGS_resourcePath[0]);
115    }
116#endif
117    this->makeDirOrFail(dir);
118
119    SkString path;
120    if (FLAGS_nameByHash) {
121        // Flat directory of hash-named files.
122        path = SkOSPath::Join(dir.c_str(), md5.c_str());
123        path.append(fExtension);
124        // We're content-addressed, so it's possible two threads race to write
125        // this file.  We let the first one win.  This also means we won't
126        // overwrite identical files from previous runs.
127        if (sk_exists(path.c_str())) {
128            return;
129        }
130    } else {
131        // Nested by mode, config, etc.
132        for (int i = 0; i < fSuffixes.count(); i++) {
133            dir = SkOSPath::Join(dir.c_str(), fSuffixes[i].c_str());
134            this->makeDirOrFail(dir);
135        }
136        path = SkOSPath::Join(dir.c_str(), fBaseName.c_str());
137        path.append(fExtension);
138        // The path is unique, so two threads can't both write to the same file.
139        // If already present we overwrite here, since the content may have changed.
140    }
141
142    SkFILEWStream file(path.c_str());
143    if (!file.isValid()) {
144        return this->fail("Can't open file.");
145    }
146
147    bool ok = fData ? file.writeStream(fData, fData->getLength())
148                    : SkImageEncoder::EncodeStream(&file, fBitmap, SkImageEncoder::kPNG_Type, 100);
149    if (!ok) {
150        return this->fail("Can't write to file.");
151    }
152}
153
154SkString WriteTask::name() const {
155    SkString name("writing ");
156    for (int i = 0; i < fSuffixes.count(); i++) {
157        name.appendf("%s/", fSuffixes[i].c_str());
158    }
159    name.append(fBaseName.c_str());
160    return name;
161}
162
163bool WriteTask::shouldSkip() const {
164    return FLAGS_writePath.isEmpty();
165}
166
167void WriteTask::DumpJson() {
168    if (FLAGS_writePath.isEmpty()) {
169        return;
170    }
171
172    Json::Value root;
173
174    for (int i = 1; i < FLAGS_properties.count(); i += 2) {
175        root[FLAGS_properties[i-1]] = FLAGS_properties[i];
176    }
177    for (int i = 1; i < FLAGS_key.count(); i += 2) {
178        root["key"][FLAGS_key[i-1]] = FLAGS_key[i];
179    }
180
181    {
182        SkAutoMutexAcquire lock(&gJsonDataLock);
183        for (int i = 0; i < gJsonData.count(); i++) {
184            Json::Value result;
185            result["key"]["name"]            = gJsonData[i].name.c_str();
186            result["key"]["config"]          = gJsonData[i].config.c_str();
187            result["key"]["mode"]            = gJsonData[i].mode.c_str();
188            result["options"]["source_type"] = gJsonData[i].sourceType.c_str();
189            result["md5"]                    = gJsonData[i].md5.c_str();
190
191            root["results"].append(result);
192        }
193    }
194
195    SkString path = SkOSPath::Join(FLAGS_writePath[0], "dm.json");
196    SkFILEWStream stream(path.c_str());
197    stream.writeText(Json::StyledWriter().write(root).c_str());
198    stream.flush();
199}
200
201}  // namespace DM
202