আমি কীভাবে সি # তে কোনও ফাইলটিতে একটি স্ট্রিম সংরক্ষণ করব?


713

আমার আছে একটি StreamReader বস্তুর যে আমি একটি স্ট্রিম নিয়ে সক্রিয়া, এখন ডিস্কে এই প্রবাহ সংরক্ষণ করুন (স্ট্রিম হতে মে চাই .gifবা .jpgবা .pdf)।

বিদ্যমান কোড:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. আমাকে এটি ডিস্কে সংরক্ষণ করতে হবে (আমার কাছে ফাইলের নাম আছে)।
  2. ভবিষ্যতে আমি এটি এসকিউএল সার্ভারে সঞ্চয় করতে চাই।

আমার কাছে এনকোডিং টাইপও আছে, যা আমি এসকিউএল সার্ভারে সঞ্চয় করে রাখলে আমার প্রয়োজন হবে, তাই না?


1
মাইআউটঅবজেক্ট কী?
anhtv13

2
এখনও এই প্রশ্নের কোন গৃহীত উত্তর?
ব্রেট রিগবি

@ ব্রিটরিগবি একটি জন স্কিটির উত্তর দিয়েছেন, এটি অনেকটা স্ব-স্বীকৃত: ডি
রিকার্ডো ডায়াস মোরেইস

উত্তর:


912

জন স্কিটির উত্তরে টাইলেন্ডর দ্বারা যেমনটি হাইলাইট করা হয়েছে,। CopyToনেট 4 থেকে স্ট্রিমগুলির একটি পদ্ধতি রয়েছে।

var fileStream = File.Create("C:\\Path\\To\\File");
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
fileStream.Close();

অথবা usingবাক্য গঠন সহ:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}

66
মনে রাখবেন যে আপনি myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)ইতিমধ্যে শুরুতে না থাকলে বা আপনি পুরো স্ট্রিমটি অনুলিপি না করে কল করতে হবে।
স্টিভ রুকুটস

3
যদি এই ইনপুট স্ট্রিমটি HT সংযোগটি থেকে পাওয়া যায় তবে এটি বাফার করবে এবং ডাউনলোড করবে এবং তারপরে উত্স থেকে সমস্ত বাইট লিখবে ?????
dbw

2
আমি যেখানে স্ট্রিমটি ব্যবহার করছি সেখানে পিডিএফ ভিউয়ার তৈরি করেছি, একবার আমি স্ট্রিমটি আবদ্ধ করি এবং যখন পিডিএফ ফাইলটি একই প্রবাহটি ব্যবহার করে সংরক্ষণ করি তখন "সিক (0, সিকোরিগিন.বিগিন)" ব্যবহার না করে আমি সঠিক দস্তাবেজ সংরক্ষণ করতে সক্ষম হবো না। তাই এটিকে +1 উল্লেখ জন্য "খোঁজ (0, SeekOrigin.Begin)"
user2463514

myOtherObject.InputStream.CopyTo (fileStream); এই লাইনটি একটি ত্রুটি দেয়: অ্যাক্সেস অস্বীকৃত।
সুলাহাদিন

2
আমারঅবর অবজেক্ট ??
হ্যারি

531

আপনার অবশ্যইStreamReader বাইনারি ফাইলগুলির জন্য ব্যবহার করা উচিত নয় (যেমন জিআইএফ বা জেপিজি)। StreamReaderজন্য টেক্সট তথ্য। আপনি প্রায় হবে অবশ্যই আপনি নির্বিচারে বাইনারি ডেটা জন্য এটি ব্যবহার করে হারান তথ্য। (আপনি যদি এনকোডিং ব্যবহার করেন etGetEncoding (28591) আপনি সম্ভবত ঠিক আছেন, তবে কী কথা?)

আপনার কেন একেবারেই দরকার StreamReader? কেন কেবল বাইনারি ডেটা বাইনারি ডেটা হিসাবে রাখি এবং এটি ডিস্কে (বা এসকিউএল) বাইনারি ডেটা হিসাবে আবার লিখব না?

সম্পাদনা: এটি দেখতে লোকেদের কিছু দেখতে চাইছে ... আপনি যদি কেবল একটি স্ট্রিমের অন্য অনুলিপি করতে চান (যেমন কোনও ফাইলের কাছে) এই জাতীয় কিছু ব্যবহার করুন:

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

একটি ফাইলের স্ট্রিম ফেলে দেওয়ার জন্য এটি ব্যবহার করতে উদাহরণস্বরূপ:

using (Stream file = File.Create(filename))
{
    CopyStream(input, file);
}

Stream.CopyTo.NET 4 এ মূলত একই উদ্দেশ্যে পরিবেশন করা হয়েছে তা নোট করুন ।


6
এটি এমন একটি সাধারণ ক্ষেত্রে বলে মনে হচ্ছে আমি। নেট এ না হয়ে অবাক হয়েছি। আমি দেখছি লোকেরা বাইট অ্যারে তৈরি করছে পুরো ফাইলের আকার, যা বড় ফাইলগুলির জন্য সমস্যা তৈরি করতে পারে।
টাইলেন্ডর

81
@ টাইলেন্ডার: এটি নেট 4 (কপিটো)
জন স্কিটি

33
আমি মনে করি এটি একটি এক্সটেনশন পদ্ধতি নয় তবে এটি স্ট্রিম ক্লাসে নতুন।
কুগেল

9
@ কুজেল: আপনি ঠিক বলেছেন, দুঃখিত। আমার কাছে এটি কোনও ইউটিলিটি লাইব্রেরিতে এক্সটেনশন পদ্ধতি হিসাবে ছিল, কিন্তু এখন এটি স্বয়ং স্ট্রিমের মধ্যে রয়েছে, আমার এক্সটেনশন পদ্ধতিটি কল হয় না।
জন স্কিটি

4
@ ফ্লোরিয়ান: এটি যুক্তিযুক্তভাবে নির্বিচারে - অত্যধিক স্মৃতি গ্রহণ করা এড়াতে একটি ছোট পরিমাণের মান এবং একবারে যুক্তিসঙ্গত অংশ স্থানান্তর করতে যথেষ্ট বড়। এটি 16 কে, 32 কে হতে পারে ঠিক হবে - আমি কেবলমাত্র বৃহত অবজেক্টের স্তূপটি না শেষ করার বিষয়ে যত্নশীল ছিলাম।
জন স্কিটি

77
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}

28
আপনার সম্ভবত streamঅবজেক্টটি using(){}ব্র্যাকেটে রাখা উচিত নয় । আপনার পদ্ধতিটি স্ট্রিম তৈরি করে নি, সুতরাং এটির নিষ্পত্তি করা উচিত নয়।
লার্সটেক

2
পরিবর্তে আপনার FileStreamব্যবহারের পরিবর্তে লাগানো দরকার , অন্যথায় এটি আবর্জনা সংগ্রহ না করা অবধি খোলা রাখা হবে।
পাভেল চিকুলাভ

আমি দেখতে পেয়েছি যে আমার অ্যাডাব্লুএস এস 3 ক্লাসের গেটওয়ে ক্লাসের সাথে উইনফোর্ডসে আমার সমস্যা সমাধানের জন্য আপনার পদ্ধতির খুব কাছাকাছি ছিল! ধন্যবাদ!
লুইজ এডুয়ার্ডো

2
এটি ঠিক আছে তবে আমি 0 কেবি আউটপুট পেয়েছি। পরিবর্তে আমি সঠিক আউটপুট এই কি ছিল: File.WriteAllBytes(destinationFilePath, input.ToArray());। আমার ক্ষেত্রে, inputএকটি এর MemoryStreamমধ্যে থেকে আসা ZipArchive
এসএনএগ

23
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}

1
এটি ঠিক আছে তবে আমি 0 কেবি আউটপুট পেয়েছি। পরিবর্তে আমি সঠিক আউটপুট এই কি ছিল: File.WriteAllBytes(destinationFilePath, input.ToArray());। আমার ক্ষেত্রে, inputএকটি এর MemoryStreamমধ্যে থেকে আসা ZipArchive
এসএনএগ

2
আমি কী ভুল করছি তা বুঝতে এটি আমাকে সহায়তা করেছিল। তবে, প্রবাহের শুরুতে যেতে ভুলবেন না: stream.Seek(0, SeekOrigin.Begin);
নাথান বিল

9

আমি সমস্ত উত্তর ব্যবহার করে পাই না CopyTo, যেখানে সম্ভবত অ্যাপ্লিকেশন ব্যবহার করা সিস্টেমগুলি .NET 4.0+ এ আপগ্রেড করা নাও হতে পারে। আমি জানি কিছু লোককে আপগ্রেড করার জন্য জোর করতে চাইবে তবে সামঞ্জস্যতাও খুব সুন্দর।

অন্য একটি জিনিস, আমি প্রথম স্থানে অন্য স্ট্রিম থেকে অনুলিপি করতে একটি স্ট্রিম ব্যবহার করি না। কেন শুধু করবেন না:

byte[] bytes = myOtherObject.InputStream.ToArray();

আপনার একবার বাইটস হয়ে গেলে আপনি সহজেই এগুলিকে একটি ফাইলে লিখতে পারেন:

public static void WriteFile(string fileName, byte[] bytes)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write))
    {
        fs.Write(bytes, 0, (int)bytes.Length);
        //fs.Close();
    }
}

এই কোডটি কোনও .jpgফাইলের সাথে এটি পরীক্ষা করার সাথে সাথে কাজ করে , যদিও আমি স্বীকার করি আমি কেবল এটি ছোট ফাইল (1 এমবি এর চেয়ে কম) দিয়ে ব্যবহার করেছি। একটি স্ট্রিম, স্ট্রিমের মধ্যে কোনও অনুলিপি নেই, কোনও এনকোডিং দরকার নেই, কেবল বাইটগুলি লিখুন! StreamReaderআপনার যদি ইতিমধ্যে কোনও স্ট্রিম থাকে তবে আপনি bytesসরাসরি রূপান্তর করতে পারেন এর সাথে জিনিসগুলিকে অতিরিক্ত জটিল করার দরকার নেই .ToArray()!

কেবলমাত্র সম্ভাব্য ডাউনসাইডগুলি আমি এটি করতে এটি দেখতে পাচ্ছি যদি আপনার কাছে একটি বড় ফাইল থাকে তবে এটি একটি স্ট্রিম হিসাবে থাকে এবং ব্যবহার .CopyTo()বা সমতুল্য হলে FileStreamএটি বাইট অ্যারে ব্যবহার না করে এবং বাইটগুলি একে একে পড়ার পরিবর্তে প্রবাহিত করতে দেয় । ফলস্বরূপ এটি এটি এইভাবে করা ধীর হতে পারে। তবে এটি বোকা লেখা উচিত নয় যেহেতু হ্যান্ডলগুলি বাইটগুলি লেখার .Write()পদ্ধতিটি নয় FileStreamএবং এটি একবারে এটি কেবল একটি বাইট করে চলেছে, সুতরাং এটি স্মৃতি আটকে রাখবে না, কেবল স্ট্রিমটি ধরে রাখতে আপনার যথেষ্ট স্মৃতি থাকতে হবেbyte[] except বস্তু । আমার যে পরিস্থিতিতে আমি এটি ব্যবহার করেছি OracleBlob, পেয়েছি, আমাকে একটিতে যেতে byte[]হয়েছিল, এটি যথেষ্ট ছোট ছিল এবং তদ্ব্যতীত, আমার কাছে কোনও স্ট্রিমিং উপলব্ধ ছিল না, যাইহোক, তাই আমি উপরে আমার ফাংশনে আমার বাইটগুলি প্রেরণ করেছি।

স্ট্রিম ব্যবহার করে অন্য একটি বিকল্প CopyStreamহ'ল এটি হ'ল জন স্কিটের ফাংশন যা এটি অন্য পোস্টে ছিল - এটি কেবল FileStreamইনপুট স্ট্রিম গ্রহণ করতে এবং সরাসরি ফাইলটি তৈরি করতে ব্যবহার করে। এটি ব্যবহার করে না File.Create, যেমন সে করেছিল (যা প্রথম দিকে আমার পক্ষে সমস্যাযুক্ত বলে মনে হয়েছিল, তবে পরে পাওয়া গেছে এটি সম্ভবত একটি ভিএস বাগ ছিল ...)।

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

public static void WriteFile(string fileName, Stream inputStream)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write)
    {
        CopyStream(inputStream, fs);
    }

    inputStream.Close();
    inputStream.Flush();
}

1
কল করার দরকার নেই Closeকারণusing()
অ্যালেক্স 78191

@ Alex78191 আপনি যদি কথা বলছেন তবে inputStream.Close()আবার দেখুন - inputStreamভেরিয়েবল হিসাবে পাঠানো হয়েছে। usingচালু থাকে path+filenameআউটপুট প্রবাহ। আপনি যদি fs.Close()মাঝখানে কথা বলছিলেন using, দুঃখিত, আপনি সে সম্পর্কে সঠিক ছিলেন এবং আমি এটি সরিয়ে দিয়েছি।
vapcguy

8
//If you don't have .Net 4.0  :)

public void SaveStreamToFile(Stream stream, string filename)
{  
   using(Stream destination = File.Create(filename))
      Write(stream, destination);
}

//Typically I implement this Write method as a Stream extension method. 
//The framework handles buffering.

public void Write(Stream from, Stream to)
{
   for(int a = from.ReadByte(); a != -1; a = from.ReadByte())
      to.WriteByte( (byte) a );
}

/*
Note, StreamReader is an IEnumerable<Char> while Stream is an IEnumbable<byte>.
The distinction is significant such as in multiple byte character encodings 
like Unicode used in .Net where Char is one or more bytes (byte[n]). Also, the
resulting translation from IEnumerable<byte> to IEnumerable<Char> can loose bytes
or insert them (for example, "\n" vs. "\r\n") depending on the StreamReader instance
CurrentEncoding.
*/

16
একটি স্ট্রিম বাইট-বাই বাইট অনুলিপি করুন (রিডবাইট / রাইডবাইট ব্যবহার করে) বাফার-বাই-বাফার (রিড (বাইট [], ইনট, ইনট) / লিখন (বাইট [], ইনট, ইনট)) ব্যবহার করার চেয়ে অনেক ধীর হবে।
কেভিন

6

ফাইলস্ট্রিম অবজেক্টটি ব্যবহার করবেন না কেন?

public void SaveStreamToFile(string fileFullPath, Stream stream)
{
    if (stream.Length == 0) return;

    // Create a FileStream object to write a stream to a file
    using (FileStream fileStream = System.IO.File.Create(fileFullPath, (int)stream.Length))
    {
        // Fill the bytes[] array with the stream data
        byte[] bytesInStream = new byte[stream.Length];
        stream.Read(bytesInStream, 0, (int)bytesInStream.Length);

        // Use FileStream object to write to the specified file
        fileStream.Write(bytesInStream, 0, bytesInStream.Length);
     }
}

46
ইনপুট স্ট্রিমটি 1 জিবি দীর্ঘ হলে কী হবে - এই কোডটি 1 জিবি বাফার বরাদ্দ দেওয়ার চেষ্টা করবে :)
বুথরাকৌর

1
এটি রেসপন্সস্ট্রিমের সাথে কাজ করছে না, কারণ এটি জ্ঞাত দৈর্ঘ্যের।
টমাস কুবেস

যদিও এটি সত্য যে আপনার কাছে স্মৃতি উপলব্ধ byte[]থাকতে হবে, আমি মনে করি এটি বিরল হবে যে আপনি কোনও ফাইলে 1 জিবি + ব্লব স্ট্রিম করছেন ... যদি আপনার কাছে এমন কোনও সাইট না থাকে যা ডিভিডি টরেন্ট রাখে ... প্লাস , আজকাল বেশিরভাগ কম্পিউটারে কমপক্ষে 2 জিবি র‌্যাম পাওয়া যায় .... ক্যাভেটটি বৈধ, তবে আমি মনে করি এটি এমন একটি ক্ষেত্রে যেখানে বেশিরভাগ কাজের ক্ষেত্রে সম্ভবত এটি "যথেষ্ট ভাল" "
vapcguy

ওয়েবসাইটে কেবলমাত্র একক ব্যবহারকারী একযোগে সক্রিয় না হলে ওয়েবসাইটগুলি এর মতো কেসটি একেবারেই ভালভাবে সহ্য করবে না।
নেট দ্য গ্রেট 21

6

আরেকটি বিকল্প হ'ল স্ট্রিমটিকে একটি করে byte[]ব্যবহার করতে হবে File.WriteAllBytes। এটি করা উচিত:

using (var stream = new MemoryStream())
{
    input.CopyTo(stream);
    File.WriteAllBytes(file, stream.ToArray());
}

এটি একটি এক্সটেনশন পদ্ধতিতে মোড়ানো এটিকে আরও ভাল নামকরণ দেয়:

public void WriteTo(this Stream input, string file)
{
    //your fav write method:

    using (var stream = File.Create(file))
    {
        input.CopyTo(stream);
    }

    //or

    using (var stream = new MemoryStream())
    {
        input.CopyTo(stream);
        File.WriteAllBytes(file, stream.ToArray());
    }

    //whatever that fits.
}

3
যদি ইনপুটটি খুব বড় হয় তবে আপনি মেমরির একটি ব্যতিক্রম পেয়ে যাবেন। ইনপুট স্ট্রিম থেকে ফাইল স্ট্রিমে অনুলিপি করার বিকল্পটি আরও ভাল
ইয়কোক

4
public void testdownload(stream input)
{
    byte[] buffer = new byte[16345];
    using (FileStream fs = new FileStream(this.FullLocalFilePath,
                        FileMode.Create, FileAccess.Write, FileShare.None))
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
             fs.Write(buffer, 0, read);
        }
    }
}

সরাসরি বাফার ইনপুট স্ট্রিম সরবরাহ করছে FileStream- দুর্দান্ত!
vapcguy

3

এখানে একটি উদাহরণ যা সঠিকভাবে ব্যবহার এবং অদম্য বাস্তবায়ন ব্যবহার করে:

static void WriteToFile(string sourceFile, string destinationfile, bool append = true, int bufferSize = 4096)
{
    using (var sourceFileStream = new FileStream(sourceFile, FileMode.OpenOrCreate))
    {
        using (var destinationFileStream = new FileStream(destinationfile, FileMode.OpenOrCreate))
        {
            while (sourceFileStream.Position < sourceFileStream.Length)
            {
                destinationFileStream.WriteByte((byte)sourceFileStream.ReadByte());
            }
        }
    }
}

... এবং এটিও আছে

    public static void WriteToFile(FileStream stream, string destinationFile, int bufferSize = 4096, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.ReadWrite)
    {
        using (var destinationFileStream = new FileStream(destinationFile, mode, access, share))
        {
            while (stream.Position < stream.Length) 
            {
                destinationFileStream.WriteByte((byte)stream.ReadByte());
            }
        }
    }

কীটি হ'ল যথাযথ ব্যবহার বোঝাচ্ছে (যা উপরে বর্ণিত হিসাবে অদৃশ্যভাবে প্রয়োগকারী বস্তুর ইনস্ট্যান্টের উপর প্রয়োগ করা উচিত), এবং কীভাবে স্রোতের জন্য বৈশিষ্ট্যগুলি কাজ করে তা সম্পর্কে একটি ভাল ধারণা রয়েছে। পজিশনটি আক্ষরিক অর্থে স্ট্রিমের মধ্যে সূচক (যা 0 থেকে শুরু হয়) যা প্রতিটি বাইট রিডবাইট পদ্ধতি ব্যবহার করে পড়া হয় followed এই ক্ষেত্রে আমি মূলত এটি লুপ ভেরিয়েবলের স্থানে ব্যবহার করছি এবং পুরো প্রবাহের শেষ প্রান্তে (বাইটে) শেষ পর্যন্ত এটি সমস্ত দৈর্ঘ্য পর্যন্ত অনুসরণ করতে দিচ্ছি। বাইটগুলিতে উপেক্ষা করুন কারণ এটি ব্যবহারিকভাবে একই এবং আপনার মতো এমন সাধারণ এবং মার্জিত কিছু থাকবে যা সবকিছু পরিষ্কারভাবে সমাধান করে।

এও মনে রাখবেন, রিডবাইট পদ্ধতিটি কেবল প্রক্রিয়ায় একটি বাইটকে কাস্ট করে এবং সহজেই ফিরে রূপান্তর করতে পারে।

আমি আরও একটি বাস্তবায়ন যুক্ত করব যা আমি সম্প্রতি প্রচুর ওভারলোড প্রতিরোধে ক্রমবর্ধমান তথ্য লিখতে নিশ্চিত করার জন্য বিভিন্ন ধরণের গতিশীল বাফার তৈরি করতে লিখেছিলাম

private void StreamBuffer(Stream stream, int buffer)
{
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        var memoryBuffer = memoryStream.GetBuffer();

        for (int i = 0; i < memoryBuffer.Length;)
        {
            var networkBuffer = new byte[buffer];
            for (int j = 0; j < networkBuffer.Length && i < memoryBuffer.Length; j++)
            {
                networkBuffer[j] = memoryBuffer[i];
                i++;
            }
            //Assuming destination file
            destinationFileStream.Write(networkBuffer, 0, networkBuffer.Length);
        }
    }
}

ব্যাখ্যাটি মোটামুটি সহজ: আমরা জানি যে আমরা যে ডেটা লিখতে চাইছি তার পুরো সেটটি আমাদের মাথায় রাখতে হবে এবং এটিও যে আমরা কেবলমাত্র কিছু পরিমাণ লিখতে চাই, তাই আমরা শেষ প্যারামিটারটি খালি (প্রথমটি হিসাবে একই সাথে) করতে চাই )। এরপরে, আমরা একটি বাইট অ্যারে বাফার প্রারম্ভিক করব যা যা পাস হয়েছে তার আকারে সেট করা আছে এবং দ্বিতীয় লুপের সাহায্যে আমরা জে বাফারের আকার এবং মূলের আকারের সাথে তুলনা করি এবং যদি এটি মূল আকারের চেয়ে বড় হয় বাইট অ্যারে, রান শেষ।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.